Merge branch 'master' into navigation_implementation

# Conflicts:
#	app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentAddItem.kt
#	app/src/main/java/com/appttude/h_mal/farmr/ui/MainActivity.kt
#	app/src/main/java/com/appttude/h_mal/farmr/ui/ShiftListAdapter.kt
#	app/src/main/java/com/appttude/h_mal/farmr/utils/Formatting.kt
This commit is contained in:
2023-09-10 17:02:13 +01:00
17 changed files with 192 additions and 61 deletions

View File

@@ -127,4 +127,4 @@ workflows:
only: only:
- release - release
requires: requires:
- build-and-test - run_instrumentation_test

View File

@@ -14,8 +14,8 @@ android {
applicationId "com.appttude.h_mal.farmr" applicationId "com.appttude.h_mal.farmr"
minSdkVersion MIN_SDK_VERSION minSdkVersion MIN_SDK_VERSION
targetSdkVersion TARGET_SDK_VERSION targetSdkVersion TARGET_SDK_VERSION
versionCode 3 versionCode 5
versionName "2.1" versionName "2.3"
testInstrumentationRunner 'com.appttude.h_mal.farmr.application.TestRunner' testInstrumentationRunner 'com.appttude.h_mal.farmr.application.TestRunner'
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }

View File

@@ -1,6 +1,5 @@
package com.appttude.h_mal.farmr.ui.robots package com.appttude.h_mal.farmr.ui.robots
import androidx.test.espresso.Espresso
import androidx.test.espresso.action.ViewActions.scrollTo import androidx.test.espresso.action.ViewActions.scrollTo
import com.appttude.h_mal.farmr.R import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.model.ShiftType import com.appttude.h_mal.farmr.model.ShiftType

View File

@@ -0,0 +1,59 @@
package com.appttude.h_mal.farmr.base
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.appttude.h_mal.farmr.utils.hide
import com.appttude.h_mal.farmr.utils.show
abstract class BaseListAdapter<T : Any>(
diff: DiffUtil.ItemCallback<T>,
private val layoutId: Int,
private val emptyView: View
) : ListAdapter<T, BaseListAdapter.CurrentViewHolder>(diff) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): CurrentViewHolder {
val currentViewHolder = LayoutInflater
.from(parent.context)
.inflate(layoutId, parent, false)
return CurrentViewHolder(currentViewHolder)
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
checkEmpty()
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
checkEmpty()
}
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
checkEmpty()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
checkEmpty()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
checkEmpty()
}
fun checkEmpty() {
if (itemCount == 0) emptyView.show()
else emptyView.hide()
}
})
}
class CurrentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}

View File

@@ -7,10 +7,10 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.appttude.h_mal.farmr.utils.generateView import com.appttude.h_mal.farmr.utils.generateView
open class BaseRecyclerAdapter<T: Any>( open class BaseRecyclerAdapter<T : Any>(
@LayoutRes private val emptyViewId: Int, @LayoutRes private val emptyViewId: Int,
@LayoutRes private val currentViewId: Int @LayoutRes private val currentViewId: Int
): RecyclerView.Adapter<ViewHolder>() { ) : RecyclerView.Adapter<ViewHolder>() {
var list: List<T>? = null var list: List<T>? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@@ -37,6 +37,6 @@ open class BaseRecyclerAdapter<T: Any>(
open fun bindEmptyView(view: View) {} open fun bindEmptyView(view: View) {}
open fun bindCurrentView(view: View, position: Int, data: T) {} open fun bindCurrentView(view: View, position: Int, data: T) {}
class EmptyViewHolder(itemView: View): ViewHolder(itemView) class EmptyViewHolder(itemView: View) : ViewHolder(itemView)
class CurrentViewHolder(itemView: View): ViewHolder(itemView) class CurrentViewHolder(itemView: View) : ViewHolder(itemView)
} }

View File

@@ -0,0 +1,46 @@
package com.appttude.h_mal.farmr.base
import android.text.Editable
import android.text.TextWatcher
import android.view.ViewGroup
import android.widget.EditText
import androidx.annotation.LayoutRes
import androidx.core.view.children
open class FormFragment<V : BaseViewModel>(@LayoutRes contentLayoutId: Int) : BaseFragment<V>(contentLayoutId) {
private val initialFormData = mutableMapOf<Int, String>()
private val formData = mutableMapOf<Int, String>()
fun applyFormListener(view: ViewGroup) {
view.children.forEach {
if (it is EditText) {
initialFormData[it.id] = it.text.trim().toString()
setDataInMap(it.id, it.text.trim().toString())
it.addCustomTextWatch()
} else if (it is ViewGroup) {
applyFormListener(it)
}
}
}
fun didFormChange(): Boolean {
return !(initialFormData.all { (k, v) ->
formData[k] == v
})
}
private fun EditText.addCustomTextWatch() {
addTextChangedListener(object : TextWatcher{
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { }
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
setDataInMap(id, p0.toString())
}
override fun afterTextChanged(p0: Editable?) { }
})
}
private fun setDataInMap(id: Int, text: String) {
formData[id] = text
}
}

View File

@@ -34,7 +34,6 @@ class FilterDataFragment : BaseFragment<FilterViewModel>(R.layout.fragment_filte
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setTitle(getString(R.string.title_activity_filter_data))
LocationET = view.findViewById(R.id.filterLocationEditText) LocationET = view.findViewById(R.id.filterLocationEditText)
dateFromET = view.findViewById(R.id.fromdateInEditText) dateFromET = view.findViewById(R.id.fromdateInEditText)
@@ -76,6 +75,16 @@ class FilterDataFragment : BaseFragment<FilterViewModel>(R.layout.fragment_filte
submit.setOnClickListener(this) submit.setOnClickListener(this)
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(false)
}
override fun onResume() {
super.onResume()
setTitle(getString(R.string.title_activity_filter_data))
}
override fun onItemSelected( override fun onItemSelected(
parentView: AdapterView<*>?, parentView: AdapterView<*>?,
selectedItemView: View?, selectedItemView: View?,

View File

@@ -2,6 +2,7 @@ package com.appttude.h_mal.farmr.ui
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.EditText import android.widget.EditText
import android.widget.LinearLayout import android.widget.LinearLayout
@@ -12,7 +13,7 @@ import android.widget.TextView
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import com.appttude.h_mal.farmr.R import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.BackPressedListener import com.appttude.h_mal.farmr.base.BackPressedListener
import com.appttude.h_mal.farmr.base.BaseFragment import com.appttude.h_mal.farmr.base.FormFragment
import com.appttude.h_mal.farmr.model.ShiftType import com.appttude.h_mal.farmr.model.ShiftType
import com.appttude.h_mal.farmr.model.Success import com.appttude.h_mal.farmr.model.Success
import com.appttude.h_mal.farmr.utils.ID import com.appttude.h_mal.farmr.utils.ID
@@ -30,7 +31,7 @@ import com.appttude.h_mal.farmr.utils.show
import com.appttude.h_mal.farmr.utils.validateField import com.appttude.h_mal.farmr.utils.validateField
import com.appttude.h_mal.farmr.viewmodel.SubmissionViewModel import com.appttude.h_mal.farmr.viewmodel.SubmissionViewModel
class FragmentAddItem : BaseFragment<SubmissionViewModel>(R.layout.fragment_add_item), class FragmentAddItem : FormFragment<SubmissionViewModel>(R.layout.fragment_add_item),
RadioGroup.OnCheckedChangeListener, BackPressedListener { RadioGroup.OnCheckedChangeListener, BackPressedListener {
private lateinit var mHourlyRadioButton: RadioButton private lateinit var mHourlyRadioButton: RadioButton
@@ -119,15 +120,29 @@ class FragmentAddItem : BaseFragment<SubmissionViewModel>(R.layout.fragment_add_
setupViewAfterViewCreated() setupViewAfterViewCreated()
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(false)
}
override fun onResume() {
super.onResume()
val title = when (arguments?.containsKey(ID)) {
true -> getString(R.string.edit_item_title)
else -> getString(R.string.add_item_title)
}
setTitle(title)
}
private fun setupViewAfterViewCreated() { private fun setupViewAfterViewCreated() {
val id = arguments?.takeIf { it.containsKey(SHIFT_ID) } val id = arguments?.takeIf { it.containsKey(SHIFT_ID) }
?.let { FragmentAddItemArgs.fromBundle(it).shiftId } ?.let { FragmentAddItemArgs.fromBundle(it).shiftId }
wholeView.hide() wholeView.hide()
val title = id?.let { if (id != null) {
// 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
viewModel.getCurrentShift(id)?.run { viewModel.getCurrentShift(arguments!!.getLong(ID))?.run {
mLocationEditText.setText(description) mLocationEditText.setText(description)
mDateEditText.setText(date) mDateEditText.setText(date)
@@ -167,9 +182,9 @@ class FragmentAddItem : BaseFragment<SubmissionViewModel>(R.layout.fragment_add_
calculateTotalPay() calculateTotalPay()
} }
getString(R.string.edit_item_title) }
} ?: getString(R.string.add_item_title)
setTitle(title) applyFormListener(view = view as ViewGroup)
} }
override fun onCheckedChanged(radioGroup: RadioGroup, id: Int) { override fun onCheckedChanged(radioGroup: RadioGroup, id: Int) {
@@ -272,17 +287,17 @@ class FragmentAddItem : BaseFragment<SubmissionViewModel>(R.layout.fragment_add_
} }
override fun onBackPressed(): Boolean { override fun onBackPressed(): Boolean {
if (mRadioGroup.checkedRadioButtonId == -1) { if (didFormChange()) {
goBack()
} else {
requireContext().createDialog( requireContext().createDialog(
title = "Discard Changes?", title = "Discard Changes?",
message = "Are you sure you want to discard changes?", message = "Are you sure you want to discard changes?",
displayCancel = true, displayCancel = true,
okCallback = { _, _ -> okCallback = { _, _ ->
mActivity?.popBackStack() goBack()
} }
) )
} else {
goBack()
} }
return true return true
} }

View File

@@ -3,6 +3,8 @@ package com.appttude.h_mal.farmr.ui
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
@@ -34,28 +36,25 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setTitle("Shift List")
// Inflate the layout for this fragment // Inflate the layout for this fragment
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
override fun onResume() {
super.onResume()
setTitle("Shift List")
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
mAdapter = ShiftListAdapter(this) { emptyView = view.findViewById(R.id.empty_view)
productListView = view.findViewById(R.id.list_item_view)
mAdapter = ShiftListAdapter(this, emptyView) {
viewModel.deleteShift(it) viewModel.deleteShift(it)
} }
productListView = view.findViewById(R.id.list_item_view)
productListView.adapter = mAdapter productListView.adapter = mAdapter
emptyView = view.findViewById(R.id.empty_view)
mAdapter.registerAdapterDataObserver(object : AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
if (mAdapter.itemCount == 0) emptyView.show()
else emptyView.hide()
}
})
view.findViewById<FloatingActionButton>(R.id.fab1).setOnClickListener { view.findViewById<FloatingActionButton>(R.id.fab1).setOnClickListener {
navigateTo(R.id.main_to_addItem) navigateTo(R.id.main_to_addItem)
@@ -67,6 +66,11 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
viewModel.refreshLiveData() viewModel.refreshLiveData()
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
// Inflate the menu; this adds items to the action bar if it is present.
inflater.inflate(R.menu.menu_main, menu)
}
override fun onSuccess(data: Any?) { override fun onSuccess(data: Any?) {
super.onSuccess(data) super.onSuccess(data)
if (data is List<*>) { if (data is List<*>) {

View File

@@ -36,7 +36,6 @@ class FurtherInfoFragment : BaseFragment<InfoViewModel>(R.layout.fragment_futher
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setTitle(getString(R.string.further_info_title))
progressBarFI = view.findViewById(R.id.progressBar_info) progressBarFI = view.findViewById(R.id.progressBar_info)
wholeView = view.findViewById(R.id.further_info_view) wholeView = view.findViewById(R.id.further_info_view)
@@ -63,6 +62,16 @@ class FurtherInfoFragment : BaseFragment<InfoViewModel>(R.layout.fragment_futher
viewModel.retrieveData(id) viewModel.retrieveData(id)
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(false)
}
override fun onResume() {
super.onResume()
setTitle(getString(R.string.further_info_title))
}
override fun onSuccess(data: Any?) { override fun onSuccess(data: Any?) {
super.onSuccess(data) super.onSuccess(data)
if (data is ShiftObject) data.setupView() if (data is ShiftObject) data.setupView()

View File

@@ -1,8 +1,5 @@
package com.appttude.h_mal.farmr.ui package com.appttude.h_mal.farmr.ui
import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@@ -31,12 +28,6 @@ class MainActivity : BaseActivity() {
navController.setGraph(R.navigation.shift_navigation) navController.setGraph(R.navigation.shift_navigation)
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onSupportNavigateUp(): Boolean { override fun onSupportNavigateUp(): Boolean {
val currentFragment = navHost.parentFragment val currentFragment = navHost.parentFragment
return if (currentFragment is BackPressedListener) { return if (currentFragment is BackPressedListener) {
@@ -50,6 +41,7 @@ class MainActivity : BaseActivity() {
} }
} }
override fun onBackPressed() { override fun onBackPressed() {
val currentFragment = supportFragmentManager.findFragmentById(R.id.container) val currentFragment = supportFragmentManager.findFragmentById(R.id.container)
if (currentFragment is BackPressedListener) { if (currentFragment is BackPressedListener) {

View File

@@ -3,17 +3,17 @@ package com.appttude.h_mal.farmr.ui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AlertDialog import android.app.AlertDialog
import android.os.Bundle import android.os.Bundle
import android.view.ViewGroup import android.view.View
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.DiffUtil 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.BaseListAdapter
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.formatToTwoDpString
import com.appttude.h_mal.farmr.utils.formatToTwoDp import com.appttude.h_mal.farmr.utils.formatToTwoDp
import com.appttude.h_mal.farmr.utils.generateView import com.appttude.h_mal.farmr.utils.generateView
import com.appttude.h_mal.farmr.utils.navigateTo import com.appttude.h_mal.farmr.utils.navigateTo
@@ -21,18 +21,12 @@ import com.appttude.h_mal.farmr.utils.navigateToFragment
class ShiftListAdapter( class ShiftListAdapter(
private val fragment: Fragment, private val fragment: Fragment,
emptyView: View,
private val longPressCallback: (Long) -> Unit private val longPressCallback: (Long) -> Unit
) : ListAdapter<ShiftObject, BaseRecyclerAdapter.CurrentViewHolder>(diffCallBack) { ) : BaseListAdapter<ShiftObject>(diffCallBack, R.layout.list_item_1, emptyView) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): BaseRecyclerAdapter.CurrentViewHolder {
val currentViewHolder = parent.generateView(R.layout.list_item_1)
return BaseRecyclerAdapter.CurrentViewHolder(currentViewHolder)
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: BaseRecyclerAdapter.CurrentViewHolder, position: Int) { override fun onBindViewHolder(holder: CurrentViewHolder, position: Int) {
val view = holder.itemView val view = holder.itemView
val data = getItem(position) val data = getItem(position)
@@ -49,7 +43,7 @@ class ShiftListAdapter(
val typeText: String = data.type val typeText: String = data.type
val descriptionText: String = data.description val descriptionText: String = data.description
val dateText: String = data.date val dateText: String = data.date
val totalPayText: String = data.totalPay.formatToTwoDp().toString() val totalPayText: String = data.totalPay.formatToTwoDpString()
descriptionTextView.text = descriptionText descriptionTextView.text = descriptionText
dateTextView.text = dateText dateTextView.text = dateText

View File

@@ -26,7 +26,7 @@ fun Float.formatAsCurrencyString(): String? {
} }
fun Float.formatToTwoDpString(): String { fun Float.formatToTwoDpString(): String {
return String.format("%.2f", this) return toBigDecimal().setScale(2).toString()
} }
fun String.dateStringIsValid(): Boolean { fun String.dateStringIsValid(): Boolean {

View File

@@ -284,6 +284,7 @@ class SubmissionViewModel(
description = description, description = description,
date = date, date = date,
units = units!!, units = units!!,
rateOfPay = rateOfPay, rateOfPay = rateOfPay,
) )
} }

View File

@@ -24,8 +24,11 @@
app:backgroundTint="@color/colorPrimary" /> app:backgroundTint="@color/colorPrimary" />
<include <include
android:visibility="gone" android:layout_centerInParent="true"
android:visibility="visible"
layout="@layout/empty_list_view" layout="@layout/empty_list_view"
android:id="@+id/empty_view"/> android:id="@+id/empty_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout> </RelativeLayout>

View File

@@ -23,7 +23,7 @@ platform :android do
desc "Deploy a new version to the Google Play" desc "Deploy a new version to the Google Play"
lane :deploy do lane :deploy do
gradle(task: "clean assembleRelease") gradle(task: "clean bundle", build_type: "Release")
upload_to_play_store upload_to_play_store
end end
end end

View File

@@ -27,8 +27,8 @@ KOTLIN_VERSION = 1.7.10
GRADLE_ANALYZE_VERSION = 1.20.0 GRADLE_ANALYZE_VERSION = 1.20.0
# Android configuration # Android configuration
COMPILE_SDK_VERSION = android-31 COMPILE_SDK_VERSION = android-33
TARGET_SDK_VERSION = 31 TARGET_SDK_VERSION = 33
MIN_SDK_VERSION = 21 MIN_SDK_VERSION = 21
# Gradle parameters # Gradle parameters