- MVVM refactor

- Kodein DI added
 - Overhaul dirty code in views
 - Bug fixes
This commit is contained in:
2023-08-26 21:43:35 +01:00
parent cd20315b32
commit 22982f1482
10 changed files with 115 additions and 64 deletions

View File

@@ -53,6 +53,6 @@ abstract class BaseActivity<V : BaseViewModel> : AppCompatActivity(), KodeinAwar
} }
fun setTitleInActionBar(title: String) { fun setTitleInActionBar(title: String) {
setTitle(title) supportActionBar?.title = title
} }
} }

View File

@@ -1,5 +1,6 @@
package com.appttude.h_mal.farmr.data.legacydb package com.appttude.h_mal.farmr.data.legacydb
import android.content.ContentResolver
import android.content.ContentUris import android.content.ContentUris
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
@@ -20,8 +21,7 @@ import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry._ID
import com.appttude.h_mal.farmr.model.Shift import com.appttude.h_mal.farmr.model.Shift
import com.appttude.h_mal.farmr.model.ShiftType import com.appttude.h_mal.farmr.model.ShiftType
class LegacyDatabase(context: Context) { class LegacyDatabase(private val resolver: ContentResolver) {
private val resolver = context.contentResolver
private val projection = arrayOf<String?>( private val projection = arrayOf<String?>(
_ID, _ID,
@@ -44,7 +44,7 @@ class LegacyDatabase(context: Context) {
val values = ContentValues().apply { val values = ContentValues().apply {
put(COLUMN_SHIFT_TYPE, shift.type.type) put(COLUMN_SHIFT_TYPE, shift.type.type)
put(COLUMN_SHIFT_DESCRIPTION, shift.description) put(COLUMN_SHIFT_DESCRIPTION, shift.description)
put(COLUMN_SHIFT_DATE, shift.description) put(COLUMN_SHIFT_DATE, shift.date)
put(COLUMN_SHIFT_TIME_IN, shift.timeIn ?: "00:00") put(COLUMN_SHIFT_TIME_IN, shift.timeIn ?: "00:00")
put(COLUMN_SHIFT_TIME_OUT, shift.timeOut ?: "00:00") put(COLUMN_SHIFT_TIME_OUT, shift.timeOut ?: "00:00")
put(COLUMN_SHIFT_DURATION, shift.duration ?: 0.00f) put(COLUMN_SHIFT_DURATION, shift.duration ?: 0.00f)
@@ -63,8 +63,9 @@ class LegacyDatabase(context: Context) {
projection, projection,
null, null, null null, null, null
) ?: return null ) ?: return null
cursor.moveToFirst() val shifts = generateSequence { if (cursor.moveToNext()) cursor else null }
val shifts = (0..cursor.count).map { cursor.getShift() } .map { it.getShift() }
.toList()
// close cursor after query operations // close cursor after query operations
cursor.close() cursor.close()

View File

@@ -19,7 +19,7 @@ class ShiftApplication: Application(), KodeinAware {
override val kodein = Kodein.lazy { override val kodein = Kodein.lazy {
import(androidXModule(this@ShiftApplication)) import(androidXModule(this@ShiftApplication))
bind() from singleton { LegacyDatabase(this@ShiftApplication) } bind() from singleton { LegacyDatabase(contentResolver) }
bind() from singleton { PreferenceProvider(this@ShiftApplication) } bind() from singleton { PreferenceProvider(this@ShiftApplication) }
bind() from singleton { RepositoryImpl(instance(), instance()) } bind() from singleton { RepositoryImpl(instance(), instance()) }

View File

@@ -51,11 +51,13 @@ class FragmentAddItem : BaseFragment<MainViewModel>(R.layout.fragment_add_item),
private var mDescription: String? = null private var mDescription: String? = null
private var mTimeIn: String? = null private var mTimeIn: String? = null
private var mTimeOut: String? = null private var mTimeOut: String? = null
private var mBreaks = 0 private var mBreaks: Int? = null
private var mUnits = 0f private var mUnits: Float? = null
private var mPayRate = 0f private var mPayRate = 0f
private var mType: ShiftType? = null private var mType: ShiftType? = null
private var mDuration: Float = 0f private var mDuration: Float? = null
private var id: Long? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@@ -97,7 +99,7 @@ class FragmentAddItem : BaseFragment<MainViewModel>(R.layout.fragment_add_item),
calculateTotalPay() calculateTotalPay()
} }
mUnitEditText.doAfterTextChanged { mUnitEditText.doAfterTextChanged {
it.toString().toFloatOrNull()?.let { u -> mPayRate = u } it.toString().toFloatOrNull()?.let { u -> mUnits = u }
calculateTotalPay() calculateTotalPay()
} }
mPayRateEditText.doAfterTextChanged { mPayRateEditText.doAfterTextChanged {
@@ -113,6 +115,8 @@ class FragmentAddItem : BaseFragment<MainViewModel>(R.layout.fragment_add_item),
} }
private fun setupViewAfterViewCreated() { private fun setupViewAfterViewCreated() {
id = arguments?.getLong(ID)
val title = when (arguments?.containsKey(ID)) { val title = when (arguments?.containsKey(ID)) {
true -> { true -> {
// Since we are editing a shift lets load the shift data into the views // Since we are editing a shift lets load the shift data into the views
@@ -158,6 +162,7 @@ class FragmentAddItem : BaseFragment<MainViewModel>(R.layout.fragment_add_item),
hourlyDataView.show() hourlyDataView.show()
durationHolder.show() durationHolder.show()
} }
R.id.piecerate -> { R.id.piecerate -> {
mType = ShiftType.PIECE mType = ShiftType.PIECE
wholeView.show() wholeView.show()
@@ -183,13 +188,48 @@ class FragmentAddItem : BaseFragment<MainViewModel>(R.layout.fragment_add_item),
} }
if (mPieceRadioButton.isChecked) { if (mPieceRadioButton.isChecked) {
mUnits.validateField({!it.isNaN()}) {
mUnits.validateField({ it != null && it >= 0 }) {
onFailure("Units field cannot be empty") onFailure("Units field cannot be empty")
return return
} }
viewModel.insertPieceRateShift(mDescription!!, mDate!!, mUnits, mPayRate) if (id != null) {
// update
viewModel.updateShift(
id!!,
description = mDescription,
date = mDate,
units = mUnits,
rateOfPay = mPayRate
)
} else {
// insert
viewModel.insertPieceRateShift(mDescription!!, mDate!!, mUnits!!, mPayRate)
}
} else if (mHourlyRadioButton.isChecked) { } else if (mHourlyRadioButton.isChecked) {
viewModel.insertHourlyShift(mDescription!!, mDate!!, mPayRate, mTimeIn, mTimeOut, mBreaks) if (id != null) {
// update
viewModel.updateShift(
id!!,
description = mDescription,
date = mDate,
rateOfPay = mPayRate,
timeIn = mTimeIn,
timeOut = mTimeOut,
breakMins = mBreaks
)
} else {
// insert
viewModel.insertHourlyShift(
mDescription!!,
mDate!!,
mPayRate,
mTimeIn,
mTimeOut,
mBreaks
)
}
} }
} }
@@ -199,11 +239,13 @@ class FragmentAddItem : BaseFragment<MainViewModel>(R.layout.fragment_add_item),
ShiftType.HOURLY -> { ShiftType.HOURLY -> {
// Calculate duration before total pay calculation // Calculate duration before total pay calculation
mDuration = viewModel.retrieveDurationText(mTimeIn, mTimeOut, mBreaks) ?: return mDuration = viewModel.retrieveDurationText(mTimeIn, mTimeOut, mBreaks) ?: return
mDurationTextView.text = StringBuilder().append(mDuration).append(" hours").toString() mDurationTextView.text =
mDuration * mPayRate StringBuilder().append(mDuration).append(" hours").toString()
mDuration!! * mPayRate
} }
ShiftType.PIECE -> { ShiftType.PIECE -> {
mUnits * mPayRate (mUnits ?: 0f) * mPayRate
} }
} }
mTotalPayTextView.text = total.formatToTwoDpString() mTotalPayTextView.text = total.formatToTwoDpString()

View File

@@ -27,7 +27,7 @@ import kotlin.system.exitProcess
class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPressedListener { class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPressedListener {
lateinit var activity: MainActivity lateinit var activity: MainActivity
private lateinit var productListView: RecyclerView private lateinit var productListView: RecyclerView
private lateinit var mAdapter: ShiftRecyclerAdapter private lateinit var mAdapter: ShiftListAdapter
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -38,7 +38,7 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
mAdapter = ShiftRecyclerAdapter(this) { mAdapter = ShiftListAdapter(this) {
viewModel.deleteShift(it) viewModel.deleteShift(it)
} }
productListView = view.findViewById(R.id.list_item_view) productListView = view.findViewById(R.id.list_item_view)
@@ -49,10 +49,16 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
} }
} }
override fun onStart() {
super.onStart()
viewModel.refreshLiveData()
}
override fun onSuccess(data: Any?) { override fun onSuccess(data: Any?) {
super.onSuccess(data) super.onSuccess(data)
if (data is List<*>) { if (data is List<*>) {
mAdapter.updateData(data as List<ShiftObject>) mAdapter.submitList(data as List<ShiftObject>)
} }
} }

View File

@@ -22,10 +22,6 @@ import kotlin.system.exitProcess
class MainActivity : BaseActivity<MainViewModel>() { class MainActivity : BaseActivity<MainViewModel>() {
private lateinit var toolbar: Toolbar private lateinit var toolbar: Toolbar
var selection: String? = null
var args: Array<String>? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.main_view) setContentView(R.layout.main_view)
@@ -57,11 +53,6 @@ class MainActivity : BaseActivity<MainViewModel>() {
} }
} }
fun setActionBarTitle(title: String?) {
toolbar.title = title
}
// Storage Permissions // Storage Permissions
private val REQUEST_EXTERNAL_STORAGE = 1 private val REQUEST_EXTERNAL_STORAGE = 1
private val PERMISSIONS_STORAGE = arrayOf( private val PERMISSIONS_STORAGE = arrayOf(

View File

@@ -2,26 +2,36 @@ package com.appttude.h_mal.farmr.ui
import android.app.AlertDialog import android.app.AlertDialog
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import com.appttude.h_mal.farmr.R import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.BaseRecyclerAdapter import com.appttude.h_mal.farmr.base.BaseRecyclerAdapter
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
import com.appttude.h_mal.farmr.model.ShiftType import com.appttude.h_mal.farmr.model.ShiftType
import com.appttude.h_mal.farmr.utils.ID import com.appttude.h_mal.farmr.utils.ID
import com.appttude.h_mal.farmr.utils.generateView
import com.appttude.h_mal.farmr.utils.navigateToFragment import com.appttude.h_mal.farmr.utils.navigateToFragment
class ShiftRecyclerAdapter( class ShiftListAdapter(
private val fragment: Fragment, private val fragment: Fragment,
private val longPressCallback: (Long) -> Unit private val longPressCallback: (Long) -> Unit
) : BaseRecyclerAdapter<ShiftObject>( ) : ListAdapter<ShiftObject, BaseRecyclerAdapter.CurrentViewHolder>(diffCallBack) {
emptyViewId = R.layout.empty_list_view, override fun onCreateViewHolder(
currentViewId = R.layout.list_item_1 parent: ViewGroup,
) { viewType: Int
override fun bindCurrentView(view: View, position: Int, data: ShiftObject) { ): BaseRecyclerAdapter.CurrentViewHolder {
val currentViewHolder = parent.generateView(R.layout.list_item_1)
return BaseRecyclerAdapter.CurrentViewHolder(currentViewHolder)
}
override fun onBindViewHolder(holder: BaseRecyclerAdapter.CurrentViewHolder, position: Int) {
val view = holder.itemView
val data = getItem(position)
val descriptionTextView: TextView = view.findViewById(R.id.location) val descriptionTextView: TextView = view.findViewById(R.id.location)
val dateTextView: TextView = view.findViewById(R.id.date) val dateTextView: TextView = view.findViewById(R.id.date)
val totalPay: TextView = view.findViewById(R.id.total_pay) val totalPay: TextView = view.findViewById(R.id.total_pay)
@@ -89,16 +99,15 @@ class ShiftRecyclerAdapter(
} }
} }
// override fun getItemId(position: Int): Long { companion object {
// return if (list.isNullOrEmpty()) { val diffCallBack = object : DiffUtil.ItemCallback<ShiftObject>() {
// RecyclerView.NO_ID override fun areItemsTheSame(oldItem: ShiftObject, newItem: ShiftObject): Boolean {
// } else { return oldItem.id == newItem.id
// list!![position].id }
// }
// override fun areContentsTheSame(oldItem: ShiftObject, newItem: ShiftObject): Boolean {
// } return oldItem == newItem
// }
// override fun setHasStableIds(hasStableIds: Boolean) { }
// super.setHasStableIds(true) }
// }
} }

View File

@@ -20,11 +20,11 @@ fun Float.formatToTwoDpString(): String {
} }
fun String.dateStringIsValid(): Boolean { fun String.dateStringIsValid(): Boolean {
return DATE_FORMAT.toPattern().matcher(this).matches() return "([0-9]{4})-([0-9]{2})-([0-9]{2})".toPattern().matcher(this).matches()
} }
fun String.timeStringIsValid(): Boolean { fun String.timeStringIsValid(): Boolean {
return TIME_FORMAT.toPattern().matcher(this).matches() return "^([0-1]?[0-9]|2[0-3]):[0-5][0-9]\$".toPattern().matcher(this).matches()
} }
fun Calendar.getTimeString(): String { fun Calendar.getTimeString(): String {

View File

@@ -173,5 +173,7 @@ fun EditText.setDatePicker(onSelected: (String) -> Unit) {
}, mYear, mMonth, mDay }, mYear, mMonth, mDay
) )
mDatePicker.setTitle("Select date") mDatePicker.setTitle("Select date")
setOnClickListener {
mDatePicker.show() mDatePicker.show()
} }
}

View File

@@ -160,7 +160,7 @@ class MainViewModel(
rateOfPay: Float rateOfPay: Float
) { ) {
// Validate inputs from the edit texts // Validate inputs from the edit texts
(description.length < 3).validateField { (description.length > 3).validateField {
onError("Description length should be longer") onError("Description length should be longer")
return return
} }
@@ -196,11 +196,11 @@ class MainViewModel(
type: String? = null, type: String? = null,
description: String? = null, description: String? = null,
date: String? = null, date: String? = null,
rateOfPay: String? = null, rateOfPay: Float? = null,
timeIn: String? = null, timeIn: String? = null,
timeOut: String? = null, timeOut: String? = null,
breakMins: String? = null, breakMins: Int? = null,
units: String? = null, units: Float? = null,
) { ) {
description?.let { description?.let {
(it.length < 3).validateField { (it.length < 3).validateField {
@@ -243,11 +243,11 @@ class MainViewModel(
type = type?.let { ShiftType.getEnumByType(it) }, type = type?.let { ShiftType.getEnumByType(it) },
description = description, description = description,
date = date, date = date,
rateOfPay = rateOfPay?.toFloatOrNull(), rateOfPay = rateOfPay,
timeIn = timeIn, timeIn = timeIn,
timeOut = timeOut, timeOut = timeOut,
breakMins = breakMins?.toIntOrNull(), breakMins = breakMins,
units = units?.toFloatOrNull() units = units
) )
} }
} }
@@ -430,7 +430,7 @@ class MainViewModel(
return textString return textString
} }
private fun refreshLiveData() { fun refreshLiveData() {
_shiftLiveData.postValue(repository.readShiftsFromDatabase()) _shiftLiveData.postValue(repository.readShiftsFromDatabase())
} }
@@ -471,7 +471,7 @@ class MainViewModel(
return mFilterStore!! return mFilterStore!!
} }
fun retrieveDurationText(mTimeIn: String?, mTimeOut: String?, mBreaks: Int): Float? { fun retrieveDurationText(mTimeIn: String?, mTimeOut: String?, mBreaks: Int?): Float? {
try { try {
return calculateDuration(mTimeIn,mTimeOut,mBreaks) return calculateDuration(mTimeIn,mTimeOut,mBreaks)
}catch (e: IOException) { }catch (e: IOException) {