diff --git a/app/build.gradle b/app/build.gradle
index 33df36f..8a92332 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,5 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'org.jetbrains.kotlin.android'
+apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 31
@@ -29,8 +30,22 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'androidx.fragment:fragment-ktx:1.4.0'
+ implementation 'androidx.activity:activity-ktx:1.4.0'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
+ implementation 'androidx.preference:preference:1.2.1'
+
implementation 'com.ajts.androidmads.SQLite2Excel:library:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:core-ktx:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
+ / * Room database * /
+ def room_version = "2.4.3"
+ implementation "androidx.room:room-runtime:$room_version"
+ kapt "androidx.room:room-compiler:$room_version"
+ implementation "androidx.room:room-ktx:$room_version"
+ / *Kodein Dependency Injection * /
+ def kodein_version = "6.2.1"
+ implementation "org.kodein.di:kodein-di-generic-jvm:$kodein_version"
+ implementation "org.kodein.di:kodein-di-framework-android-x:$kodein_version"
}
diff --git a/app/src/androidTest/java/com/appttude/h_mal/farmr/data/ShiftProviderTest.kt b/app/src/androidTest/java/com/appttude/h_mal/farmr/data/ShiftProviderTest.kt
index d8872a5..67a4c51 100644
--- a/app/src/androidTest/java/com/appttude/h_mal/farmr/data/ShiftProviderTest.kt
+++ b/app/src/androidTest/java/com/appttude/h_mal/farmr/data/ShiftProviderTest.kt
@@ -3,19 +3,20 @@ package com.appttude.h_mal.farmr.data
import android.content.ContentResolver
import android.content.ContentValues
import androidx.test.rule.provider.ProviderTestRule
-import com.appttude.h_mal.farmr.data.ShiftsContract.CONTENT_AUTHORITY
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_BREAK
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DATE
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DESCRIPTION
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DURATION
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_PAYRATE
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_IN
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_OUT
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TOTALPAY
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TYPE
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_UNIT
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.CONTENT_URI
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry._ID
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.CONTENT_AUTHORITY
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_BREAK
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DATE
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DESCRIPTION
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DURATION
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_PAYRATE
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_IN
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_OUT
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TOTALPAY
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TYPE
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_UNIT
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.CONTENT_URI
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry._ID
+import com.appttude.h_mal.farmr.data.legacydb.ShiftProvider
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNull
import org.junit.Rule
@@ -75,6 +76,8 @@ class ShiftProviderTest {
// Assert
val item = contentResolver.query(CONTENT_URI, projection, null, null, null)
item?.takeIf { it.moveToNext() }?.run {
+ val id = getLong(getColumnIndexOrThrow(_ID))
+
val descriptionColumnIndex = getString(getColumnIndexOrThrow(COLUMN_SHIFT_DESCRIPTION))
val dateColumnIndex = getString(getColumnIndexOrThrow(COLUMN_SHIFT_DATE))
val timeInColumnIndex = getString(getColumnIndexOrThrow(COLUMN_SHIFT_TIME_IN))
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8dc1d6f..12b8ee2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -7,6 +7,7 @@
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/FilterDataFragment.kt b/app/src/main/java/com/appttude/h_mal/farmr/FilterDataFragment.kt
deleted file mode 100644
index 78945fb..0000000
--- a/app/src/main/java/com/appttude/h_mal/farmr/FilterDataFragment.kt
+++ /dev/null
@@ -1,144 +0,0 @@
-package com.appttude.h_mal.farmr
-
-import android.app.DatePickerDialog
-import android.os.Bundle
-import android.text.TextUtils
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ArrayAdapter
-import android.widget.Button
-import android.widget.EditText
-import android.widget.Spinner
-import androidx.fragment.app.Fragment
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
-import java.text.MessageFormat
-import java.text.SimpleDateFormat
-import java.util.Calendar
-import java.util.Date
-
-class FilterDataFragment : Fragment() {
- private val spinnerList: Array = arrayOf("", "Hourly", "Piece Rate")
- private val listArgs: MutableList = ArrayList()
- private var LocationET: EditText? = null
- private var dateFromET: EditText? = null
- private var dateToET: EditText? = null
- private var typeSpinner: Spinner? = null
- lateinit var mcurrentDate: Calendar
- private lateinit var activity: MainActivity
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View {
- val activity = (activity)
-
- // Inflate the layout for this fragment
- val rootView: View = inflater.inflate(R.layout.fragment_filter_data, container, false)
- activity.setActionBarTitle(getString(R.string.title_activity_filter_data))
- mcurrentDate = Calendar.getInstance()
- LocationET = rootView.findViewById(R.id.filterLocationEditText) as EditText?
- dateFromET = rootView.findViewById(R.id.fromdateInEditText) as EditText?
- dateToET = rootView.findViewById(R.id.filterDateOutEditText) as EditText?
- typeSpinner = rootView.findViewById(R.id.TypeFilterEditText) as Spinner?
-
-
- if (activity.selection != null && activity.selection!!.contains(" AND " + ShiftsEntry.COLUMN_SHIFT_DESCRIPTION + " LIKE ?")) {
- var str: String = activity.args!!.get(2)
- str = str.replace("%", "")
- LocationET!!.setText(str)
- }
- if (activity.selection != null && !(activity.args!!.get(0) == "2000-01-01")) {
- dateFromET!!.setText(activity.args!!.get(0))
- }
- if ((activity.selection != null) && (activity.args != null) && !(activity.args!![1] == (mcurrentDate.get(Calendar.YEAR).toString() + "-"
- + String.format("%02d", (mcurrentDate.get(Calendar.MONTH) + 1)) + "-"
- + String.format("%02d", mcurrentDate.get(Calendar.DAY_OF_MONTH))).toString())) {
- dateToET!!.setText(activity.args!![1])
- }
- dateFromET!!.setOnClickListener { //To show current date in the datepicker
- var mYear: Int = mcurrentDate.get(Calendar.YEAR)
- var mMonth: Int = mcurrentDate.get(Calendar.MONTH)
- var mDay: Int = mcurrentDate.get(Calendar.DAY_OF_MONTH)
- if (!(dateFromET!!.text.toString() == "")) {
- val dateString: String = dateFromET!!.text.toString().trim()
- mYear = dateString.substring(0, 4).toInt()
- mMonth = dateString.substring(5, 7).toInt()
- if (mMonth == 1) {
- mMonth = 0
- } else {
- mMonth = mMonth - 1
- }
- mDay = dateString.substring(8).toInt()
- }
-
- val mDatePicker = DatePickerDialog((context)!!, { datepicker, selectedyear, selectedmonth, selectedday ->
- val input = MessageFormat.format("{0}-{1}-{2}", selectedyear, String.format("%02d", (selectedmonth + 1)), String.format("%02d", selectedday))
- dateFromET!!.setText(input)
- }, mYear, mMonth, mDay)
- mDatePicker.setTitle("Select date")
- mDatePicker.show()
- }
- dateToET!!.setOnClickListener { //To show current date in the datepicker
- val mcurrentDate: Calendar = Calendar.getInstance()
- var mYear: Int = mcurrentDate.get(Calendar.YEAR)
- var mMonth: Int = mcurrentDate.get(Calendar.MONTH)
- var mDay: Int = mcurrentDate.get(Calendar.DAY_OF_MONTH)
- if (!(dateToET!!.text.toString() == "")) {
- val dateString: String = dateToET!!.text.toString().trim({ it <= ' ' })
- mYear = dateString.substring(0, 4).toInt()
- mMonth = dateString.substring(5, 7).toInt()
- if (mMonth == 1) {
- mMonth = 0
- } else {
- mMonth -= 1
- }
- mDay = dateString.substring(8).toInt()
- }
- val mDatePicker = DatePickerDialog((context)!!, { datepicker, selectedyear, selectedmonth, selectedday ->
- val input = MessageFormat.format("{0}-{1}-{2}", selectedyear, String.format("%02d", (selectedmonth + 1)), String.format("%02d", selectedday))
- dateToET!!.setText(input)
- }, mYear, mMonth, mDay)
- mDatePicker.setTitle("Select date")
- mDatePicker.show()
- }
- val adapter: ArrayAdapter = ArrayAdapter((context)!!, android.R.layout.simple_spinner_dropdown_item, spinnerList)
- typeSpinner!!.adapter = adapter
- if (activity.selection != null && activity.selection!!.contains(" AND " + ShiftsEntry.COLUMN_SHIFT_TYPE + " IS ?")) {
- val spinnerPosition: Int = adapter.getPosition(activity.args!!.get(activity.args!!.size - 1))
- typeSpinner!!.setSelection(spinnerPosition)
- }
- val submit: Button = rootView.findViewById(R.id.submitFiltered) as Button
- submit.setOnClickListener {
- BuildQuery()
- activity.args = listArgs.toTypedArray()
- FragmentMain.NEW_LOADER = 1
- activity.fragmentManager!!.popBackStack()
- }
- return rootView
- }
-
- private fun BuildQuery() {
- val dateQuery: String = ShiftsEntry.COLUMN_SHIFT_DATE + " BETWEEN ? AND ?"
- val descQuery: String = " AND " + ShiftsEntry.COLUMN_SHIFT_DESCRIPTION + " LIKE ?"
- val typeQuery: String = " AND " + ShiftsEntry.COLUMN_SHIFT_TYPE + " IS ?"
- var dateFrom = "2000-01-01"
- val c: Date = Calendar.getInstance().time
- val df = SimpleDateFormat("yyyy-MM-dd")
- var dateTo: String = df.format(c)
- if (!TextUtils.isEmpty(dateFromET!!.text.toString().trim())) {
- dateFrom = dateFromET!!.text.toString().trim()
- }
- if (!TextUtils.isEmpty(dateToET!!.text.toString().trim())) {
- dateTo = dateToET!!.text.toString().trim()
- }
- activity.selection = dateQuery
- listArgs.add(dateFrom)
- listArgs.add(dateTo)
- if (!TextUtils.isEmpty(LocationET!!.text.toString().trim())) {
- activity.selection = activity.selection + descQuery
- listArgs.add("%" + LocationET!!.text.toString().trim() + "%")
- }
- if (!(typeSpinner!!.selectedItem.toString() == "")) {
- activity.selection = activity.selection + typeQuery
- listArgs.add(typeSpinner!!.selectedItem.toString())
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/FragmentAddItem.kt b/app/src/main/java/com/appttude/h_mal/farmr/FragmentAddItem.kt
deleted file mode 100644
index eedcee5..0000000
--- a/app/src/main/java/com/appttude/h_mal/farmr/FragmentAddItem.kt
+++ /dev/null
@@ -1,507 +0,0 @@
-package com.appttude.h_mal.farmr
-
-import android.app.DatePickerDialog
-import android.app.DatePickerDialog.OnDateSetListener
-import android.app.TimePickerDialog
-import android.app.TimePickerDialog.OnTimeSetListener
-import android.content.ContentValues
-import android.database.Cursor
-import android.net.Uri
-import android.os.Bundle
-import android.text.Editable
-import android.text.TextUtils
-import android.text.TextWatcher
-import android.view.LayoutInflater
-import android.view.Menu
-import android.view.MenuInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.DatePicker
-import android.widget.EditText
-import android.widget.LinearLayout
-import android.widget.ProgressBar
-import android.widget.RadioButton
-import android.widget.RadioGroup
-import android.widget.ScrollView
-import android.widget.TextView
-import android.widget.TimePicker
-import android.widget.Toast
-import androidx.fragment.app.Fragment
-import androidx.loader.app.LoaderManager
-import androidx.loader.content.CursorLoader
-import androidx.loader.content.Loader
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
-import java.util.Calendar
-
-class FragmentAddItem : Fragment(), LoaderManager.LoaderCallbacks {
- lateinit var activity: MainActivity
-
- private var mCurrentProductUri: Uri? = null
- private var mRadioButtonOne: RadioButton? = null
- private var mRadioButtonTwo: RadioButton? = null
- private var mLocationEditText: EditText? = null
- private var mDateEditText: EditText? = null
- private var mDurationTextView: TextView? = null
- private var mTimeInEditText: EditText? = null
- private var mTimeOutEditText: EditText? = null
- private var mBreakEditText: EditText? = null
- private var mUnitEditText: EditText? = null
- private var mPayRateEditText: EditText? = null
- private var mTotalPayTextView: TextView? = null
- private var hourlyDataView: LinearLayout? = null
- private var unitsHolder: LinearLayout? = null
- private var durationHolder: LinearLayout? = null
- private var wholeView: LinearLayout? = null
- private var scrollView: ScrollView? = null
- private var progressBarAI: ProgressBar? = null
- private var mBreaks = 0
- private var mDay = 0
- private var mMonth = 0
- private var mYear = 0
- private var mHoursIn = 0
- private var mMinutesIn = 0
- private var mHoursOut = 0
- private var mMinutesOut = 0
- private var mUnits = 0f
- private var mPayRate = 0f
- private var mType: String? = null
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View? {
- // Inflate the layout for this fragment
- val rootView = inflater.inflate(R.layout.fragment_add_item, container, false)
- setHasOptionsMenu(true)
- activity = (requireActivity() as MainActivity)
-
- progressBarAI = rootView.findViewById(R.id.pd_ai) as ProgressBar
- scrollView = rootView.findViewById(R.id.total_view) as ScrollView
- mRadioGroup = rootView.findViewById(R.id.rg) as RadioGroup
- mRadioButtonOne = rootView.findViewById(R.id.hourly) as RadioButton
- mRadioButtonTwo = rootView.findViewById(R.id.piecerate) as RadioButton
- mLocationEditText = rootView.findViewById(R.id.locationEditText) as EditText
- mDateEditText = rootView.findViewById(R.id.dateEditText) as EditText
- mTimeInEditText = rootView.findViewById(R.id.timeInEditText) as EditText
- mBreakEditText = rootView.findViewById(R.id.breakEditText) as EditText
- mTimeOutEditText = rootView.findViewById(R.id.timeOutEditText) as EditText
- mDurationTextView = rootView.findViewById(R.id.ShiftDuration) as TextView
- mUnitEditText = rootView.findViewById(R.id.unitET) as EditText
- mPayRateEditText = rootView.findViewById(R.id.payrateET) as EditText
- mTotalPayTextView = rootView.findViewById(R.id.totalpayval) as TextView
- hourlyDataView = rootView.findViewById(R.id.hourly_data_holder) as LinearLayout
- unitsHolder = rootView.findViewById(R.id.units_holder) as LinearLayout
- durationHolder = rootView.findViewById(R.id.duration_holder) as LinearLayout
- wholeView = rootView.findViewById(R.id.whole_view) as LinearLayout
- mPayRate = 0.0f
- mUnits = 0.0f
- val b = arguments
- if (b != null) {
- mCurrentProductUri = Uri.parse(b.getString("uri"))
- }
- if (mCurrentProductUri == null) {
- activity.setActionBarTitle(getString(R.string.add_item_title))
- wholeView!!.visibility = View.GONE
- } else {
- activity.setActionBarTitle(getString(R.string.edit_item_title))
- loaderManager.initLoader(EXISTING_PRODUCT_LOADER, null, this)
- }
- mBreakEditText!!.hint = "insert break in minutes"
- mRadioGroup!!.setOnCheckedChangeListener(RadioGroup.OnCheckedChangeListener { radioGroup, i ->
- if (mRadioButtonOne!!.isChecked) {
- mType = mRadioButtonOne!!.text.toString()
- wholeView!!.visibility = View.VISIBLE
- unitsHolder!!.visibility = View.GONE
- hourlyDataView!!.visibility = View.VISIBLE
- durationHolder!!.visibility = View.VISIBLE
- } else if (mRadioButtonTwo!!.isChecked) {
- mType = mRadioButtonTwo!!.text.toString()
- wholeView!!.visibility = View.VISIBLE
- unitsHolder!!.visibility = View.VISIBLE
- hourlyDataView!!.visibility = View.GONE
- durationHolder!!.visibility = View.GONE
- }
- })
- mDateEditText!!.setOnClickListener(object : View.OnClickListener {
- override fun onClick(v: View) {
- //To show current date in the datepicker
- if (TextUtils.isEmpty(mDateEditText!!.text.toString().trim { it <= ' ' })) {
- val mcurrentDate = Calendar.getInstance()
- mYear = mcurrentDate[Calendar.YEAR]
- mMonth = mcurrentDate[Calendar.MONTH]
- mDay = mcurrentDate[Calendar.DAY_OF_MONTH]
- } else {
- val dateString = mDateEditText!!.text.toString().trim { it <= ' ' }
- mYear = dateString.substring(0, 4).toInt()
- mMonth = dateString.substring(5, 7).toInt()
- if (mMonth == 1) {
- mMonth = 0
- } else {
- mMonth = mMonth - 1
- }
- mDay = dateString.substring(8).toInt()
- }
- val mDatePicker = DatePickerDialog((context)!!, object : OnDateSetListener {
- override fun onDateSet(datepicker: DatePicker, selectedyear: Int, selectedmonth: Int, selectedday: Int) {
- var selectedmonth = selectedmonth
- mDateEditText!!.setText(
- (selectedyear.toString() + "-"
- + String.format("%02d", (selectedmonth + 1.also { selectedmonth = it })) + "-"
- + String.format("%02d", selectedday))
- )
- setDate(selectedyear, selectedmonth, selectedday)
- }
- }, mYear, mMonth, mDay)
- mDatePicker.setTitle("Select date")
- mDatePicker.show()
- }
- })
- mTimeInEditText!!.setOnClickListener(object : View.OnClickListener {
- override fun onClick(v: View) {
- if ((mTimeInEditText!!.text.toString() == "")) {
- val mcurrentTime = Calendar.getInstance()
- mHoursIn = mcurrentTime[Calendar.HOUR_OF_DAY]
- mMinutesIn = mcurrentTime[Calendar.MINUTE]
- } else {
- mHoursIn = (mTimeInEditText!!.text.toString().subSequence(0, 2)).toString().toInt()
- mMinutesIn = (mTimeInEditText!!.text.toString().subSequence(3, 5)).toString().toInt()
- }
- val mTimePicker: TimePickerDialog
- mTimePicker = TimePickerDialog(context, object : OnTimeSetListener {
- override fun onTimeSet(timePicker: TimePicker, selectedHour: Int, selectedMinute: Int) {
- val ddTime = String.format("%02d", selectedHour) + ":" + String.format("%02d", selectedMinute)
- setTime(selectedMinute, selectedHour)
- mTimeInEditText!!.setText(ddTime)
- }
- }, mHoursIn, mMinutesIn, true) //Yes 24 hour time
- mTimePicker.setTitle("Select Time")
- mTimePicker.show()
- }
- })
- mTimeOutEditText!!.setOnClickListener(object : View.OnClickListener {
- override fun onClick(v: View) {
- if ((mTimeOutEditText!!.text.toString() == "")) {
- val mcurrentTime = Calendar.getInstance()
- mHoursOut = mcurrentTime[Calendar.HOUR_OF_DAY]
- mMinutesOut = mcurrentTime[Calendar.MINUTE]
- } else {
- mHoursOut = (mTimeOutEditText!!.text.toString().subSequence(0, 2)).toString().toInt()
- mMinutesOut = (mTimeOutEditText!!.text.toString().subSequence(3, 5)).toString().toInt()
- }
- val mTimePicker: TimePickerDialog
- mTimePicker = TimePickerDialog(context, object : OnTimeSetListener {
- override fun onTimeSet(timePicker: TimePicker, selectedHour: Int, selectedMinute: Int) {
- val ddTime = String.format("%02d", selectedHour) + ":" + String.format("%02d", selectedMinute)
- setTime2(selectedMinute, selectedHour)
- mTimeOutEditText!!.setText(ddTime)
- }
- }, mHoursOut, mMinutesOut, true) //Yes 24 hour time
- mTimePicker.setTitle("Select Time")
- mTimePicker.show()
- }
- })
- mTimeInEditText!!.addTextChangedListener(object : TextWatcher {
- override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
- override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, aft: Int) {}
- override fun afterTextChanged(s: Editable) {
- setDuration()
- calculateTotalPay()
- }
- })
- mTimeOutEditText!!.addTextChangedListener(object : TextWatcher {
- override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
- override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, aft: Int) {}
- override fun afterTextChanged(s: Editable) {
- setDuration()
- calculateTotalPay()
- }
- })
- mBreakEditText!!.addTextChangedListener(object : TextWatcher {
- override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
- override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, aft: Int) {}
- override fun afterTextChanged(s: Editable) {
- setDuration()
- calculateTotalPay()
- }
- })
- mUnitEditText!!.addTextChangedListener(object : TextWatcher {
- override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun afterTextChanged(editable: Editable) {
-// if(mRadioButtonTwo.isChecked()) {
-// mUnits = 0.0f;
-// if (!mUnitEditText.getText().toString().equals("")){
-// mUnits = Float.parseFloat(mUnitEditText.getText().toString());
-// }
-// mPayRate = 0.0f;
-// if (!mPayRateEditText.getText().toString().equals("")){
-// mPayRate = Float.parseFloat(mPayRateEditText.getText().toString());
-// }
-// Float total = mPayRate * mUnits;
-// mTotalPayTextView.setText(String.valueOf(total));
-// }
- calculateTotalPay()
- }
- })
- mPayRateEditText!!.addTextChangedListener(object : TextWatcher {
- override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun afterTextChanged(editable: Editable) {
- calculateTotalPay()
- }
- })
- val SubmitProduct = rootView.findViewById(R.id.submit) as Button
- SubmitProduct.setOnClickListener(object : View.OnClickListener {
- override fun onClick(view: View) {
- saveProduct()
- }
- })
- return rootView
- }
-
- private fun calculateTotalPay() {
- var total = 0.0f
- mPayRate = 0.0f
- if (mRadioButtonTwo!!.isChecked) {
- mUnits = 0.0f
- if (mUnitEditText!!.text.toString() != "") {
- mUnits = mUnitEditText!!.text.toString().toFloat()
- }
- if (mPayRateEditText!!.text.toString() != "") {
- mPayRate = mPayRateEditText!!.text.toString().toFloat()
- }
- total = mPayRate * mUnits
- mTotalPayTextView!!.text = total.toString()
- } else if (mRadioButtonOne!!.isChecked) {
- if (mPayRateEditText!!.text.toString() != "") {
- mPayRate = mPayRateEditText!!.text.toString().toFloat()
- total = mPayRate * calculateDuration(mHoursIn, mMinutesIn, mHoursOut, mMinutesOut, mBreaks)
- }
- }
- mTotalPayTextView!!.text = String.format("%.2f", total)
- }
-
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- super.onCreateOptionsMenu(menu, inflater)
- menu.clear()
- }
-
- private fun saveProduct() {
- val typeString = mType
- val descriptionString = mLocationEditText!!.text.toString().trim { it <= ' ' }
- if (TextUtils.isEmpty(descriptionString)) {
- Toast.makeText(context, "please insert Location", Toast.LENGTH_SHORT).show()
- return
- }
- val dateString = mDateEditText!!.text.toString().trim { it <= ' ' }
- if (TextUtils.isEmpty(dateString)) {
- Toast.makeText(context, "please insert Date", Toast.LENGTH_SHORT).show()
- return
- }
- var timeInString: String = ""
- var timeOutString: String = ""
- var breaks = 0
- var units = 0f
- var duration = 0f
- var payRate = 0f
- val payRateString = mPayRateEditText!!.text.toString().trim { it <= ' ' }
- if (!TextUtils.isEmpty(payRateString)) {
- payRate = payRateString.toFloat()
- }
- var totalPay = 0f
- if ((typeString == "Hourly")) {
- timeInString = mTimeInEditText!!.text.toString().trim { it <= ' ' }
- if (TextUtils.isEmpty(timeInString)) {
- Toast.makeText(context, "please insert Time in", Toast.LENGTH_SHORT).show()
- return
- }
- timeOutString = mTimeOutEditText!!.text.toString().trim { it <= ' ' }
- if (TextUtils.isEmpty(timeOutString)) {
- Toast.makeText(context, "please insert Time out", Toast.LENGTH_SHORT).show()
- return
- }
- val breakMins = mBreakEditText!!.text.toString().trim { it <= ' ' }
- if (!TextUtils.isEmpty(breakMins)) {
- breaks = breakMins.toInt()
- if ((breaks.toFloat() / 60) > calculateDurationWithoutBreak(mHoursIn, mMinutesIn, mHoursOut, mMinutesOut)) {
- Toast.makeText(context, "Break larger than duration", Toast.LENGTH_SHORT).show()
- return
- }
- }
- duration = calculateDuration(mHoursIn, mMinutesIn, mHoursOut, mMinutesOut, breaks)
- totalPay = duration * payRate
- } else if ((typeString == "Piece Rate")) {
- val unitsString = mUnitEditText!!.text.toString().trim { it <= ' ' }
- if (TextUtils.isEmpty(unitsString) || unitsString.toFloat() <= 0) {
- Toast.makeText(context, "Insert Units", Toast.LENGTH_SHORT).show()
- return
- } else {
- units = unitsString.toFloat()
- }
- duration = 0f
- totalPay = units * payRate
- }
- val values = ContentValues()
- values.put(ShiftsEntry.COLUMN_SHIFT_TYPE, typeString)
- values.put(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION, descriptionString)
- values.put(ShiftsEntry.COLUMN_SHIFT_DATE, dateString)
- values.put(ShiftsEntry.COLUMN_SHIFT_TIME_IN, timeInString)
- values.put(ShiftsEntry.COLUMN_SHIFT_TIME_OUT, timeOutString)
- values.put(ShiftsEntry.COLUMN_SHIFT_DURATION, duration)
- values.put(ShiftsEntry.COLUMN_SHIFT_BREAK, breaks)
- values.put(ShiftsEntry.COLUMN_SHIFT_UNIT, units)
- values.put(ShiftsEntry.COLUMN_SHIFT_PAYRATE, payRate)
- values.put(ShiftsEntry.COLUMN_SHIFT_TOTALPAY, totalPay)
- if (mCurrentProductUri == null) {
- val newUri = activity.contentResolver.insert(ShiftsEntry.CONTENT_URI, values)
- if (newUri == null) {
- Toast.makeText(context, getString(R.string.insert_item_failed),
- Toast.LENGTH_SHORT).show()
- } else {
- Toast.makeText(context, getString(R.string.insert_item_successful),
- Toast.LENGTH_SHORT).show()
- }
- } else {
- val rowsAffected = activity.contentResolver.update(mCurrentProductUri!!, values, null, null)
- if (rowsAffected == 0) {
- Toast.makeText(context, getString(R.string.update_item_failed),
- Toast.LENGTH_SHORT).show()
- } else {
- Toast.makeText(context, getString(R.string.update_item_successful),
- Toast.LENGTH_SHORT).show()
- }
- }
- (activity.fragmentManager)!!.popBackStack("main", 0)
- }
-
- private fun setDuration() {
- val mcurrentTime = Calendar.getInstance()
- mBreaks = 0
- if (mBreakEditText!!.text.toString() != "") {
- mBreaks = mBreakEditText!!.text.toString().toInt()
- }
- if ((mTimeOutEditText!!.text.toString() == "")) {
- mHoursOut = mcurrentTime[Calendar.HOUR_OF_DAY]
- mMinutesOut = mcurrentTime[Calendar.MINUTE]
- } else {
- mHoursOut = (mTimeOutEditText!!.text.toString().subSequence(0, 2)).toString().toInt()
- mMinutesOut = (mTimeOutEditText!!.text.toString().subSequence(3, 5)).toString().toInt()
- }
- if ((mTimeInEditText!!.text.toString() == "")) {
- mHoursIn = mcurrentTime[Calendar.HOUR_OF_DAY]
- mMinutesIn = mcurrentTime[Calendar.MINUTE]
- } else {
- mHoursIn = (mTimeInEditText!!.text.toString().subSequence(0, 2)).toString().toInt()
- mMinutesIn = (mTimeInEditText!!.text.toString().subSequence(3, 5)).toString().toInt()
- }
- mDurationTextView!!.text = calculateDuration(mHoursIn, mMinutesIn, mHoursOut, mMinutesOut, mBreaks).toString() + " hours"
- }
-
- private fun setDate(year: Int, month: Int, day: Int) {
- mYear = year
- mMonth = month
- mDay = day
- }
-
- private fun setTime(minutes: Int, hours: Int) {
- mMinutesIn = minutes
- mHoursIn = hours
- }
-
- private fun setTime2(minutes: Int, hours: Int) {
- mMinutesOut = minutes
- mHoursOut = hours
- }
-
- private fun calculateDuration(hoursIn: Int, minutesIn: Int, hoursOut: Int, minutesOut: Int, breaks: Int): Float {
- val duration: Float
- if (hoursOut > hoursIn) {
- duration = ((hoursOut.toFloat() + (minutesOut.toFloat() / 60)) - (hoursIn.toFloat() + (minutesIn.toFloat() / 60))) - (breaks.toFloat() / 60)
- } else {
- duration = (((hoursOut.toFloat() + (minutesOut.toFloat() / 60)) - (hoursIn.toFloat() + (minutesIn.toFloat() / 60))) - (breaks.toFloat() / 60) + 24)
- }
- val s = String.format("%.2f", duration)
- return s.toFloat()
- }
-
- private fun calculateDurationWithoutBreak(hoursIn: Int, minutesIn: Int, hoursOut: Int, minutesOut: Int): Float {
- val duration: Float
- if (hoursOut > hoursIn) {
- duration = ((hoursOut.toFloat() + (minutesOut.toFloat() / 60)) - (hoursIn.toFloat() + (minutesIn.toFloat() / 60)))
- } else {
- duration = (((hoursOut.toFloat() + (minutesOut.toFloat() / 60)) - (hoursIn.toFloat() + (minutesIn.toFloat() / 60))) + 24)
- }
- val s = String.format("%.2f", duration)
- return s.toFloat()
- }
-
- override fun onCreateLoader(id: Int, args: Bundle?): Loader {
- progressBarAI!!.visibility = View.VISIBLE
- scrollView!!.visibility = View.GONE
- val projection = arrayOf(
- ShiftsEntry._ID,
- ShiftsEntry.COLUMN_SHIFT_DESCRIPTION,
- ShiftsEntry.COLUMN_SHIFT_DATE,
- ShiftsEntry.COLUMN_SHIFT_TIME_IN,
- ShiftsEntry.COLUMN_SHIFT_TIME_OUT,
- ShiftsEntry.COLUMN_SHIFT_BREAK,
- ShiftsEntry.COLUMN_SHIFT_DURATION,
- ShiftsEntry.COLUMN_SHIFT_TYPE,
- ShiftsEntry.COLUMN_SHIFT_PAYRATE,
- ShiftsEntry.COLUMN_SHIFT_UNIT,
- ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
- return CursorLoader((context)!!, (mCurrentProductUri)!!,
- projection, null, null, null)
- }
-
- override fun onLoaderReset(loader: Loader) {}
-
- override fun onLoadFinished(loader: Loader, cursor: Cursor?) {
- progressBarAI!!.visibility = View.GONE
- scrollView!!.visibility = View.VISIBLE
- if (cursor == null || cursor.count < 1) {
- return
- }
- if (cursor.moveToFirst()) {
- val descriptionColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION)
- val dateColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_DATE)
- val timeInColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TIME_IN)
- val timeOutColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TIME_OUT)
- val breakColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_BREAK)
- val durationColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_DURATION)
- val typeColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TYPE)
- val unitColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_UNIT)
- val payrateColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_PAYRATE)
- val totalPayColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
- val type = cursor.getString(typeColumnIndex)
- val description = cursor.getString(descriptionColumnIndex)
- val date = cursor.getString(dateColumnIndex)
- val timeIn = cursor.getString(timeInColumnIndex)
- val timeOut = cursor.getString(timeOutColumnIndex)
- val breaks = cursor.getInt(breakColumnIndex)
- val duration = cursor.getFloat(durationColumnIndex)
- val unit = cursor.getFloat(unitColumnIndex)
- val payrate = cursor.getFloat(payrateColumnIndex)
- val totalPay = cursor.getFloat(totalPayColumnIndex)
- mLocationEditText!!.setText(description)
- mDateEditText!!.setText(date)
- if ((type == "Hourly") || (type == "hourly")) {
- mRadioButtonOne!!.isChecked = true
- mRadioButtonTwo!!.isChecked = false
- mTimeInEditText!!.setText(timeIn)
- mTimeOutEditText!!.setText(timeOut)
- mBreakEditText!!.setText(Integer.toString(breaks))
- mDurationTextView!!.text = String.format("%.2f", duration) + " Hours"
- } else if ((type == "Piece Rate")) {
- mRadioButtonOne!!.isChecked = false
- mRadioButtonTwo!!.isChecked = true
- mUnitEditText!!.setText(java.lang.Float.toString(unit))
- }
- mPayRateEditText!!.setText(String.format("%.2f", payrate))
- mTotalPayTextView!!.text = String.format("%.2f", totalPay)
- }
- }
-
- companion object {
- private val EXISTING_PRODUCT_LOADER = 0
- var mRadioGroup: RadioGroup? = null
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/FragmentMain.kt b/app/src/main/java/com/appttude/h_mal/farmr/FragmentMain.kt
deleted file mode 100644
index 2b09b09..0000000
--- a/app/src/main/java/com/appttude/h_mal/farmr/FragmentMain.kt
+++ /dev/null
@@ -1,459 +0,0 @@
-package com.appttude.h_mal.farmr
-
-import android.Manifest
-import android.annotation.SuppressLint
-import android.app.Activity
-import android.app.AlertDialog
-import android.content.ContentValues
-import android.content.DialogInterface
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.database.Cursor
-import android.net.Uri
-import android.os.Bundle
-import android.os.Environment
-import android.os.StrictMode
-import android.os.StrictMode.VmPolicy
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ListView
-import android.widget.Toast
-import androidx.core.app.ActivityCompat
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentTransaction
-import androidx.loader.app.LoaderManager
-import androidx.loader.content.CursorLoader
-import androidx.loader.content.Loader
-import com.ajts.androidmads.library.SQLiteToExcel
-import com.ajts.androidmads.library.SQLiteToExcel.ExportListener
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
-import com.appttude.h_mal.farmr.data.ShiftsDbHelper
-import com.google.android.material.floatingactionbutton.FloatingActionButton
-import java.io.File
-
-class FragmentMain : Fragment(), LoaderManager.LoaderCallbacks {
- var mCursorAdapter: ShiftsCursorAdapter? = null
- var shiftsDbhelper: ShiftsDbHelper? = null
- lateinit var defaultLoaderCallback: LoaderManager.LoaderCallbacks
- lateinit var activity: MainActivity
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View? {
- // Inflate the layout for this fragment
- val rootView = inflater.inflate(R.layout.fragment_main, container, false)
- setHasOptionsMenu(true)
- activity = (requireActivity() as MainActivity)
-
- activity.setActionBarTitle(getString(R.string.app_name))
- activity.filter = activity.getSharedPreferences("PREFS", 0)
- activity.sortOrder = activity.filter?.getString("Filter", null)
- defaultLoaderCallback = this
- val productListView = rootView.findViewById(R.id.list_item_view) as ListView
- val emptyView = rootView.findViewById(R.id.empty_view)
- productListView.emptyView = emptyView
- mCursorAdapter = ShiftsCursorAdapter(activity, null)
- productListView.adapter = mCursorAdapter
- loaderManager.initLoader(DEFAULT_LOADER, null, defaultLoaderCallback)
- val fab = rootView.findViewById(R.id.fab1)
- fab.setOnClickListener {
- val fragmentTransaction: FragmentTransaction = activity.fragmentManager!!.beginTransaction()
- fragmentTransaction.replace(R.id.container, FragmentAddItem()).addToBackStack("additem").commit()
- }
- return rootView
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.delete_all -> {
- deleteAllProducts()
- return true
- }
-
- R.id.help -> {
- AlertDialog.Builder(context)
- .setTitle("Help & Support:")
- .setView(R.layout.dialog_layout)
- .setPositiveButton(android.R.string.yes) { arg0, arg1 -> }.create().show()
- return true
- }
-
- R.id.filter_data -> {
- val fragmentTransaction: FragmentTransaction = activity.fragmentManager!!.beginTransaction()
- fragmentTransaction.replace(R.id.container, FilterDataFragment()).addToBackStack("filterdata").commit()
- return true
- }
-
- R.id.sort_data -> {
- sortData()
- return true
- }
-
- R.id.clear_filter -> {
- activity.args = null
- activity.selection = null
- NEW_LOADER = 0
- loaderManager.restartLoader(DEFAULT_LOADER, null, this)
- return true
- }
-
- R.id.export_data -> {
- if (checkStoragePermissions(activity)) {
- AlertDialog.Builder(context)
- .setTitle("Export?")
- .setMessage("Exporting current filtered data. Continue?")
- .setNegativeButton(android.R.string.no, null)
- .setPositiveButton(android.R.string.yes) { arg0, arg1 -> ExportData() }.create().show()
- } else {
- Toast.makeText(context, "Storage permissions required", Toast.LENGTH_SHORT).show()
- }
- return true
- }
-
- R.id.action_favorite -> {
- AlertDialog.Builder(context)
- .setTitle("Info:")
- .setMessage(retrieveInfo())
- .setPositiveButton(android.R.string.yes) { arg0, arg1 -> }.create().show()
- return true
- }
- }
- return super.onOptionsItemSelected(item)
- }
-
- private fun sortData() {
- val grpname = arrayOf("Added", "Date", "Name")
- val sortQuery = arrayOf("")
- var checkedItem = -1
- if (activity.sortOrder != null && activity.sortOrder!!.contains(ShiftsEntry._ID)) {
- checkedItem = 0
- sortQuery[0] = ShiftsEntry._ID
- } else if (activity.sortOrder != null && activity.sortOrder!!.contains(ShiftsEntry.COLUMN_SHIFT_DATE)) {
- checkedItem = 1
- sortQuery[0] = ShiftsEntry.COLUMN_SHIFT_DATE
- } else if (activity.sortOrder != null && activity.sortOrder!!.contains(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION)) {
- checkedItem = 2
- sortQuery[0] = ShiftsEntry.COLUMN_SHIFT_DESCRIPTION
- }
- val alt_bld = AlertDialog.Builder(context)
- //alt_bld.setIcon(R.drawable.icon);
- alt_bld.setTitle("Sort by:")
- alt_bld.setSingleChoiceItems(grpname, checkedItem, DialogInterface.OnClickListener { dialog, item ->
- when (item) {
- 0 -> {
- sortQuery[0] = ShiftsEntry._ID
- return@OnClickListener
- }
-
- 1 -> {
- sortQuery[0] = ShiftsEntry.COLUMN_SHIFT_DATE
- return@OnClickListener
- }
-
- 2 -> sortQuery[0] = ShiftsEntry.COLUMN_SHIFT_DESCRIPTION
- }
- }).setPositiveButton("Ascending") { dialog, id ->
- activity.sortOrder = sortQuery[0] + " ASC"
- activity.filter!!.edit().putString("Filter", activity.sortOrder).apply()
- loaderManager.restartLoader(DEFAULT_LOADER, null, defaultLoaderCallback)
- dialog.dismiss()
- }.setNegativeButton("Descending") { dialog, id ->
- activity.sortOrder = sortQuery[0] + " DESC"
- activity.filter!!.edit().putString("Filter", activity.sortOrder).apply()
- loaderManager.restartLoader(DEFAULT_LOADER, null, defaultLoaderCallback)
- dialog.dismiss()
- }
- val alert = alt_bld.create()
- alert.show()
- }
-
- private fun deleteAllProducts() {
- AlertDialog.Builder(context)
- .setTitle("Delete?")
- .setMessage("Are you sure you want to delete all date?")
- .setNegativeButton(android.R.string.no, null)
- .setPositiveButton(android.R.string.yes) { arg0, arg1 ->
- val rowsDeleted = activity.contentResolver.delete(ShiftsEntry.CONTENT_URI, null, null)
- Toast.makeText(context, "$rowsDeleted Items Deleted", Toast.LENGTH_SHORT).show()
- }.create().show()
- }
-
- override fun onResume() {
- super.onResume()
- if (NEW_LOADER > DEFAULT_LOADER) {
- loaderManager.restartLoader(DEFAULT_LOADER, null, defaultLoaderCallback)
- println("reloading loader")
- }
- }
-
- private fun ExportData() {
- val permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
- if (permission != PackageManager.PERMISSION_GRANTED) {
- Toast.makeText(context, "Storage permissions not granted", Toast.LENGTH_SHORT).show()
- return
- }
- shiftsDbhelper = ShiftsDbHelper(context)
- val database = shiftsDbhelper!!.writableDatabase
- val projection_export = arrayOf(
- ShiftsEntry.COLUMN_SHIFT_DESCRIPTION,
- ShiftsEntry.COLUMN_SHIFT_DATE,
- ShiftsEntry.COLUMN_SHIFT_TIME_IN,
- ShiftsEntry.COLUMN_SHIFT_TIME_OUT,
- ShiftsEntry.COLUMN_SHIFT_BREAK,
- ShiftsEntry.COLUMN_SHIFT_DURATION,
- ShiftsEntry.COLUMN_SHIFT_TYPE,
- ShiftsEntry.COLUMN_SHIFT_UNIT,
- ShiftsEntry.COLUMN_SHIFT_PAYRATE,
- ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
- val cursor = activity.contentResolver.query(
- ShiftsEntry.CONTENT_URI,
- projection_export,
- activity.selection,
- activity.args,
- activity.sortOrder)
- database.delete(ShiftsEntry.TABLE_NAME_EXPORT, null, null)
- var totalDuration = 0.00f
- var totalUnits = 0.00f
- var totalPay = 0.00f
- try {
- while (cursor!!.moveToNext()) {
- val descriptionColumnIndex = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION))
- val dateColumnIndex = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DATE))
- val timeInColumnIndex = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TIME_IN))
- val timeOutColumnIndex = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TIME_OUT))
- val durationColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DURATION))
- val breakOutColumnIndex = cursor.getInt(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_BREAK))
- val typeColumnIndex = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TYPE))
- val unitColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_UNIT))
- val payrateColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_PAYRATE))
- val totalpayColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TOTALPAY))
- totalUnits = totalUnits + unitColumnIndex
- totalDuration = totalDuration + durationColumnIndex
- totalPay = totalPay + totalpayColumnIndex
- val values = ContentValues()
- values.put(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION, descriptionColumnIndex)
- values.put(ShiftsEntry.COLUMN_SHIFT_DATE, dateColumnIndex)
- values.put(ShiftsEntry.COLUMN_SHIFT_TIME_IN, timeInColumnIndex)
- values.put(ShiftsEntry.COLUMN_SHIFT_TIME_OUT, timeOutColumnIndex)
- values.put(ShiftsEntry.COLUMN_SHIFT_BREAK, breakOutColumnIndex)
- values.put(ShiftsEntry.COLUMN_SHIFT_DURATION, durationColumnIndex)
- values.put(ShiftsEntry.COLUMN_SHIFT_TYPE, typeColumnIndex)
- values.put(ShiftsEntry.COLUMN_SHIFT_UNIT, unitColumnIndex)
- values.put(ShiftsEntry.COLUMN_SHIFT_PAYRATE, payrateColumnIndex)
- values.put(ShiftsEntry.COLUMN_SHIFT_TOTALPAY, totalpayColumnIndex)
- database.insert(ShiftsEntry.TABLE_NAME_EXPORT, null, values)
- }
- } catch (e: Exception) {
- Log.e("FragmentMain", "ExportData: ", e)
- } finally {
- val values = ContentValues()
- values.put(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION, "")
- values.put(ShiftsEntry.COLUMN_SHIFT_DATE, "")
- values.put(ShiftsEntry.COLUMN_SHIFT_TIME_IN, "")
- values.put(ShiftsEntry.COLUMN_SHIFT_TIME_OUT, "")
- values.put(ShiftsEntry.COLUMN_SHIFT_BREAK, "Total duration:")
- values.put(ShiftsEntry.COLUMN_SHIFT_DURATION, totalDuration)
- values.put(ShiftsEntry.COLUMN_SHIFT_TYPE, "Total units:")
- values.put(ShiftsEntry.COLUMN_SHIFT_UNIT, totalUnits)
- values.put(ShiftsEntry.COLUMN_SHIFT_PAYRATE, "Total pay:")
- values.put(ShiftsEntry.COLUMN_SHIFT_TOTALPAY, totalPay)
- database.insert(ShiftsEntry.TABLE_NAME_EXPORT, null, values)
- cursor!!.close()
- }
- val savePath = Environment.getExternalStorageDirectory().toString() + "/ShifttrackerTemp"
- val file = File(savePath)
- if (!file.exists()) {
- file.mkdirs()
- }
- val sqLiteToExcel = SQLiteToExcel(context, "shifts.db", savePath)
- sqLiteToExcel.exportSingleTable("shiftsexport", "shifthistory.xls", object : ExportListener {
- override fun onStart() {}
- override fun onCompleted(filePath: String) {
- Toast.makeText(context, filePath, Toast.LENGTH_SHORT).show()
- val newPath = Uri.parse("file://$savePath/shifthistory.xls")
- val builder = VmPolicy.Builder()
- StrictMode.setVmPolicy(builder.build())
- val emailintent = Intent(Intent.ACTION_SEND)
- emailintent.type = "application/vnd.ms-excel"
- emailintent.putExtra(Intent.EXTRA_SUBJECT, "historic shifts")
- emailintent.putExtra(Intent.EXTRA_TEXT, "I'm email body.")
- emailintent.putExtra(Intent.EXTRA_STREAM, newPath)
- startActivity(Intent.createChooser(emailintent, "Send Email"))
- }
-
- override fun onError(e: Exception) {
- println("Error msg: $e")
- Toast.makeText(context, "Failed to Export data", Toast.LENGTH_SHORT).show()
- }
- })
- }
-
- private fun retrieveInfo(): String {
- val projection = arrayOf(
- ShiftsEntry._ID,
- ShiftsEntry.COLUMN_SHIFT_DESCRIPTION,
- ShiftsEntry.COLUMN_SHIFT_DATE,
- ShiftsEntry.COLUMN_SHIFT_TIME_IN,
- ShiftsEntry.COLUMN_SHIFT_TIME_OUT,
- ShiftsEntry.COLUMN_SHIFT_BREAK,
- ShiftsEntry.COLUMN_SHIFT_DURATION,
- ShiftsEntry.COLUMN_SHIFT_TYPE,
- ShiftsEntry.COLUMN_SHIFT_UNIT,
- ShiftsEntry.COLUMN_SHIFT_PAYRATE,
- ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
- val cursor = activity.contentResolver.query(
- ShiftsEntry.CONTENT_URI,
- projection,
- activity.selection,
- activity.args,
- activity.sortOrder)
- var totalDuration = 0.0f
- var countOfTypeH = 0
- var countOfTypeP = 0
- var totalUnits = 0f
- var totalPay = 0f
- var lines = 0
- try {
- while (cursor!!.moveToNext()) {
- val durationColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DURATION))
- val typeColumnIndex = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TYPE))
- val unitColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_UNIT))
- val totalpayColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TOTALPAY))
- totalDuration = totalDuration + durationColumnIndex
- if (typeColumnIndex.contains("Hourly")) {
- countOfTypeH = countOfTypeH + 1
- } else if (typeColumnIndex.contains("Piece")) {
- countOfTypeP = countOfTypeP + 1
- }
- totalUnits = totalUnits + unitColumnIndex
- totalPay = totalPay + totalpayColumnIndex
- }
- } finally {
- if (cursor != null && cursor.count > 0) {
- lines = cursor.count
- cursor.close()
- }
- }
- return buildInfoString(totalDuration, countOfTypeH, countOfTypeP, totalUnits, totalPay, lines)
- }
-
- @SuppressLint("DefaultLocale")
- fun buildInfoString(totalDuration: Float, countOfTypeH: Int, countOfTypeP: Int, totalUnits: Float, totalPay: Float, lines: Int): String {
- var textString: String
- textString = "$lines Shifts"
- if (countOfTypeH != 0 && countOfTypeP != 0) {
- textString = "$textString ($countOfTypeH Hourly/$countOfTypeP Piece Rate)"
- }
- if (countOfTypeH != 0) {
- textString = """
- $textString
- Total Hours: ${String.format("%.2f", totalDuration)}
- """.trimIndent()
- }
- if (countOfTypeP != 0) {
- textString = """
- $textString
- Total Units: ${String.format("%.2f", totalUnits)}
- """.trimIndent()
- }
- if (totalPay != 0f) {
- textString = """
- $textString
- Total Pay: ${"$"}${String.format("%.2f", totalPay)}
- """.trimIndent()
- }
- return textString
- }
-
- override fun onCreateLoader(i: Int, bundle: Bundle?): Loader {
- val projection = arrayOf(
- ShiftsEntry._ID,
- ShiftsEntry.COLUMN_SHIFT_DESCRIPTION,
- ShiftsEntry.COLUMN_SHIFT_DATE,
- ShiftsEntry.COLUMN_SHIFT_TIME_IN,
- ShiftsEntry.COLUMN_SHIFT_TIME_OUT,
- ShiftsEntry.COLUMN_SHIFT_BREAK,
- ShiftsEntry.COLUMN_SHIFT_DURATION,
- ShiftsEntry.COLUMN_SHIFT_TYPE,
- ShiftsEntry.COLUMN_SHIFT_PAYRATE,
- ShiftsEntry.COLUMN_SHIFT_UNIT,
- ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
- return CursorLoader(context!!,
- ShiftsEntry.CONTENT_URI,
- projection,
- activity.selection,
- activity.args,
- activity.sortOrder)
- }
-
- override fun onLoadFinished(loader: Loader, cursor: Cursor) {
- mCursorAdapter!!.swapCursor(cursor)
- }
-
- override fun onLoaderReset(loader: Loader) {
- mCursorAdapter!!.swapCursor(null)
- }
-
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- println("request code$requestCode")
- if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) {
- if (grantResults.size > 0
- && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- exportDialog()
- } else {
- Toast.makeText(context, "Storage Permissions denied", Toast.LENGTH_SHORT).show()
- }
- }
- }
-
- fun exportDialog() {
- AlertDialog.Builder(context)
- .setTitle("Export?")
- .setMessage("Exporting current filtered data. Continue?")
- .setNegativeButton(android.R.string.no, null)
- .setPositiveButton(android.R.string.yes) { arg0, arg1 -> ExportData() }.create().show()
- }
-
- companion object {
- const val MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
- private const val DEFAULT_LOADER = 0
- var NEW_LOADER = 0
-
- // // Storage Permissions
- // private static final int REQUEST_EXTERNAL_STORAGE = 1;
- // private static String[] PERMISSIONS_STORAGE = {
- // Manifest.permission.READ_EXTERNAL_STORAGE,
- // Manifest.permission.WRITE_EXTERNAL_STORAGE
- // };
- // /**
- // * Checks if the app has permission to write to device storage
- // *
- // * If the app does not has permission then the user will be prompted to grant permissions
- // *
- // * @param activity
- // */
- // public static void verifyStoragePermissions(Activity activity) {
- // // Check if we have write permission
- // int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
- //
- // if (permission != PackageManager.PERMISSION_GRANTED) {
- // // We don't have permission so prompt the user
- // ActivityCompat.requestPermissions(
- // activity,
- // PERMISSIONS_STORAGE,
- // REQUEST_EXTERNAL_STORAGE
- // );
- // }
- // }
- fun checkStoragePermissions(activity: Activity?): Boolean {
- var status = false
- val permission = ActivityCompat.checkSelfPermission(activity!!, Manifest.permission.WRITE_EXTERNAL_STORAGE)
- if (permission == PackageManager.PERMISSION_GRANTED) {
- status = true
- }
- return status
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/FurtherInfoFragment.kt b/app/src/main/java/com/appttude/h_mal/farmr/FurtherInfoFragment.kt
deleted file mode 100644
index eedc41d..0000000
--- a/app/src/main/java/com/appttude/h_mal/farmr/FurtherInfoFragment.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-package com.appttude.h_mal.farmr
-
-import android.database.Cursor
-import android.net.Uri
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.Menu
-import android.view.MenuInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.LinearLayout
-import android.widget.ProgressBar
-import android.widget.TextView
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentTransaction
-import androidx.loader.app.LoaderManager
-import androidx.loader.content.CursorLoader
-import androidx.loader.content.Loader
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
-
-class FurtherInfoFragment : Fragment(), LoaderManager.LoaderCallbacks {
- private var typeTV: TextView? = null
- private var descriptionTV: TextView? = null
- private var dateTV: TextView? = null
- private var times: TextView? = null
- private var breakTV: TextView? = null
- private var durationTV: TextView? = null
- private var unitsTV: TextView? = null
- private var payRateTV: TextView? = null
- private var totalPayTV: TextView? = null
- private var hourlyDetailHolder: LinearLayout? = null
- private var unitsHolder: LinearLayout? = null
- private var wholeView: LinearLayout? = null
- private var progressBarFI: ProgressBar? = null
- private var editButton: Button? = null
- private var CurrentUri: Uri? = null
-
- lateinit var activity: MainActivity
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View {
- // Inflate the layout for this fragment
- val rootView: View = inflater.inflate(R.layout.fragment_futher_info, container, false)
- setHasOptionsMenu(true)
- activity = (requireActivity() as MainActivity)
- activity.setActionBarTitle(getString(R.string.further_info_title))
-
- progressBarFI = rootView.findViewById(R.id.progressBar_info) as ProgressBar?
- wholeView = rootView.findViewById(R.id.further_info_view) as LinearLayout?
- typeTV = rootView.findViewById(R.id.details_shift) as TextView?
- descriptionTV = rootView.findViewById(R.id.details_desc) as TextView?
- dateTV = rootView.findViewById(R.id.details_date) as TextView?
- times = rootView.findViewById(R.id.details_time) as TextView?
- breakTV = rootView.findViewById(R.id.details_breaks) as TextView?
- durationTV = rootView.findViewById(R.id.details_duration) as TextView?
- unitsTV = rootView.findViewById(R.id.details_units) as TextView?
- payRateTV = rootView.findViewById(R.id.details_pay_rate) as TextView?
- totalPayTV = rootView.findViewById(R.id.details_totalpay) as TextView?
- editButton = rootView.findViewById(R.id.details_edit) as Button?
- hourlyDetailHolder = rootView.findViewById(R.id.details_hourly_details) as LinearLayout?
- unitsHolder = rootView.findViewById(R.id.details_units_holder) as LinearLayout?
- val b: Bundle? = arguments
- CurrentUri = Uri.parse(b!!.getString("uri"))
- loaderManager.initLoader(DEFAULT_LOADER, null, this)
- editButton!!.setOnClickListener(object : View.OnClickListener {
- override fun onClick(view: View) {
- val fragmentTransaction: FragmentTransaction = (activity.fragmentManager)!!.beginTransaction()
- val fragment: Fragment = FragmentAddItem()
- fragment.arguments = b
- fragmentTransaction.replace(R.id.container, fragment).addToBackStack("additem").commit()
- }
- })
- return rootView
- }
-
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- super.onCreateOptionsMenu(menu, inflater)
- menu.clear()
- }
-
- override fun onCreateLoader(id: Int, args: Bundle?): Loader {
- progressBarFI!!.visibility = View.VISIBLE
- wholeView!!.visibility = View.GONE
- val projection: Array = arrayOf(
- ShiftsEntry._ID,
- ShiftsEntry.COLUMN_SHIFT_DESCRIPTION,
- ShiftsEntry.COLUMN_SHIFT_DATE,
- ShiftsEntry.COLUMN_SHIFT_TIME_IN,
- ShiftsEntry.COLUMN_SHIFT_TIME_OUT,
- ShiftsEntry.COLUMN_SHIFT_BREAK,
- ShiftsEntry.COLUMN_SHIFT_DURATION,
- ShiftsEntry.COLUMN_SHIFT_TYPE,
- ShiftsEntry.COLUMN_SHIFT_PAYRATE,
- ShiftsEntry.COLUMN_SHIFT_UNIT,
- ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
- return CursorLoader((context)!!, (CurrentUri)!!,
- projection, null, null, null)
- }
-
- override fun onLoadFinished(loader: Loader, cursor: Cursor) {
- progressBarFI!!.visibility = View.GONE
- wholeView!!.visibility = View.VISIBLE
- if (cursor == null || cursor.count < 1) {
- return
- }
- if (cursor.moveToFirst()) {
- val descriptionColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION)
- val dateColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_DATE)
- val timeInColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TIME_IN)
- val timeOutColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TIME_OUT)
- val breakColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_BREAK)
- val durationColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_DURATION)
- val typeColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TYPE)
- val unitColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_UNIT)
- val payrateColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_PAYRATE)
- val totalPayColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
- val type: String = cursor.getString(typeColumnIndex)
- val description: String = cursor.getString(descriptionColumnIndex)
- val date: String = cursor.getString(dateColumnIndex)
- val timeIn: String = cursor.getString(timeInColumnIndex)
- val timeOut: String = cursor.getString(timeOutColumnIndex)
- val breaks: Int = cursor.getInt(breakColumnIndex)
- val duration: Float = cursor.getFloat(durationColumnIndex)
- val unit: Float = cursor.getFloat(unitColumnIndex)
- val payrate: Float = cursor.getFloat(payrateColumnIndex)
- val totalPay: Float = cursor.getFloat(totalPayColumnIndex)
- var durationString: String = ShiftsCursorAdapter.Companion.timeValues(duration).get(0) + " Hours " + ShiftsCursorAdapter.Companion.timeValues(duration).get(1) + " Minutes "
- if (breaks != 0) {
- durationString = durationString + " (+ " + Integer.toString(breaks) + " minutes break)"
- }
- typeTV!!.text = type
- descriptionTV!!.text = description
- dateTV!!.text = date
- var totalPaid: String? = ""
- val currency: String = "$"
- if ((type == "Hourly")) {
- hourlyDetailHolder!!.visibility = View.VISIBLE
- unitsHolder!!.visibility = View.GONE
- times!!.text = timeIn + " - " + timeOut
- breakTV!!.text = Integer.toString(breaks) + "mins"
- durationTV!!.text = durationString
- totalPaid = (String.format("%.2f", duration) + " Hours @ " + currency + String.format("%.2f", payrate) + " per Hour" + "\n"
- + "Equals: " + currency + String.format("%.2f", totalPay))
- } else if ((type == "Piece Rate")) {
- hourlyDetailHolder!!.visibility = View.GONE
- unitsHolder!!.visibility = View.VISIBLE
- unitsTV!!.text = String.format("%.2f", unit)
- totalPaid = (String.format("%.2f", unit) + " Units @ " + currency + String.format("%.2f", payrate) + " per Unit" + "\n"
- + "Equals: " + currency + String.format("%.2f", totalPay))
- }
- payRateTV!!.text = String.format("%.2f", payrate)
- totalPayTV!!.text = totalPaid
- }
- }
-
- override fun onLoaderReset(loader: Loader) {}
-
- companion object {
- private val DEFAULT_LOADER: Int = 0
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/MainActivity.kt b/app/src/main/java/com/appttude/h_mal/farmr/MainActivity.kt
deleted file mode 100644
index a0cbd9c..0000000
--- a/app/src/main/java/com/appttude/h_mal/farmr/MainActivity.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-package com.appttude.h_mal.farmr
-
-import android.Manifest
-import android.app.Activity
-import android.app.AlertDialog
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.content.pm.PackageManager
-import android.os.Bundle
-import android.view.Menu
-import android.view.View
-import androidx.appcompat.app.AppCompatActivity
-import androidx.appcompat.widget.Toolbar
-import androidx.core.app.ActivityCompat
-import androidx.fragment.app.FragmentManager
-
-class MainActivity : AppCompatActivity() {
- var filter: SharedPreferences? = null
- var context: Context? = null
- var sortOrder: String? = null
- var selection: String? = null
- var args: Array? = null
- private var toolbar: Toolbar? = null
- var fragmentManager: FragmentManager? = null
- private var currentFragment: String? = null
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.main_view)
- verifyStoragePermissions(this)
- toolbar = findViewById(R.id.toolbar) as Toolbar
- setSupportActionBar(toolbar)
- fragmentManager = supportFragmentManager
- val fragmentTransaction = fragmentManager?.beginTransaction()
- fragmentTransaction?.replace(R.id.container, FragmentMain())?.addToBackStack("main")?.commit()
- fragmentManager?.addOnBackStackChangedListener {
- val f = fragmentManager?.fragments
- val frag = f?.get(0)
- currentFragment = frag?.javaClass?.simpleName
- }
- }
-
- 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 onBackPressed() {
- when (currentFragment) {
- "FragmentMain" -> {
- AlertDialog.Builder(this)
- .setTitle("Leave?")
- .setMessage("Are you sure you want to exit Farmr?")
- .setNegativeButton(android.R.string.no, null)
- .setPositiveButton(android.R.string.yes) { arg0, arg1 ->
- val intent = Intent(Intent.ACTION_MAIN)
- intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
- intent.addCategory(Intent.CATEGORY_HOME)
- startActivity(intent)
- finish()
- System.exit(0)
- }.create().show()
- return
- }
-
- "FragmentAddItem" -> {
- if (FragmentAddItem.Companion.mRadioGroup!!.checkedRadioButtonId == -1) {
- fragmentManager!!.popBackStack()
- } else {
- AlertDialog.Builder(this)
- .setTitle("Discard Changes?")
- .setMessage("Are you sure you want to discard changes?")
- .setNegativeButton(android.R.string.no, null)
- .setPositiveButton(android.R.string.yes) { arg0, arg1 -> fragmentManager!!.popBackStack() }.create().show()
- }
- return
- }
-
- else -> if (fragmentManager!!.backStackEntryCount > 1) {
- fragmentManager!!.popBackStack()
- }
- }
- }
-
- fun setActionBarTitle(title: String?) {
- toolbar!!.title = title
- }
-
- // Storage Permissions
- private val REQUEST_EXTERNAL_STORAGE = 1
- private val PERMISSIONS_STORAGE = arrayOf(
- Manifest.permission.READ_EXTERNAL_STORAGE,
- Manifest.permission.WRITE_EXTERNAL_STORAGE
- )
-
- /**
- * Checks if the app has permission to write to device storage
- *
- * If the app does not has permission then the user will be prompted to grant permissions
- *
- * @param activity
- */
- fun verifyStoragePermissions(activity: Activity?) {
- // Check if we have write permission
- val permission = ActivityCompat.checkSelfPermission(activity!!, Manifest.permission.WRITE_EXTERNAL_STORAGE)
- if (permission != PackageManager.PERMISSION_GRANTED) {
- // We don't have permission so prompt the user
- ActivityCompat.requestPermissions(
- activity,
- PERMISSIONS_STORAGE,
- REQUEST_EXTERNAL_STORAGE
- )
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/ShiftsCursorAdapter.kt b/app/src/main/java/com/appttude/h_mal/farmr/ShiftsCursorAdapter.kt
deleted file mode 100644
index 0163f95..0000000
--- a/app/src/main/java/com/appttude/h_mal/farmr/ShiftsCursorAdapter.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-package com.appttude.h_mal.farmr
-
-import android.app.AlertDialog
-import android.content.ContentUris
-import android.content.Context
-import android.content.DialogInterface
-import android.database.Cursor
-import android.net.Uri
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.OnLongClickListener
-import android.view.ViewGroup
-import android.widget.CursorAdapter
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.fragment.app.FragmentTransaction
-import com.appttude.h_mal.farmr.data.ShiftProvider
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
-import kotlin.math.floor
-
-/**
- * Created by h_mal on 26/12/2017.
- */
-class ShiftsCursorAdapter constructor(private val activity: MainActivity, c: Cursor?) : CursorAdapter(activity, c, 0) {
- private var mContext: Context? = null
- var shiftProvider: ShiftProvider? = null
- override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
- return LayoutInflater.from(context).inflate(R.layout.list_item_1, parent, false)
- }
-
- override fun bindView(view: View, context: Context, cursor: Cursor) {
- mContext = context
- val descriptionTextView: TextView = view.findViewById(R.id.location) as TextView
- val dateTextView: TextView = view.findViewById(R.id.date) as TextView
- val totalPay: TextView = view.findViewById(R.id.total_pay) as TextView
- val hoursView: TextView = view.findViewById(R.id.hours) as TextView
- val h: TextView = view.findViewById(R.id.h) as TextView
- val minutesView: TextView = view.findViewById(R.id.minutes) as TextView
- val m: TextView = view.findViewById(R.id.m) as TextView
- val editView: ImageView = view.findViewById(R.id.imageView) as ImageView
- h.text = "h"
- m.text = "m"
- val typeColumnIndex: String = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TYPE))
- val descriptionColumnIndex: String = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION))
- val dateColumnIndex: String = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DATE))
- val durationColumnIndex: Float = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DURATION))
- val unitsColumnIndex: Float = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_UNIT))
- val totalpayColumnIndex: Float = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TOTALPAY))
- descriptionTextView.text = descriptionColumnIndex
- dateTextView.text = newDate(dateColumnIndex)
- totalPay.text = String.format("%.2f", totalpayColumnIndex)
- if ((typeColumnIndex == "Piece Rate") && durationColumnIndex == 0f) {
- hoursView.text = unitsColumnIndex.toString()
- h.text = ""
- minutesView.text = ""
- m.text = "pcs"
- } else // if(typeColumnIndex.equals("Hourly") || typeColumnIndex.equals("hourly"))
- {
- hoursView.text = timeValues(durationColumnIndex).get(0)
- minutesView.text = timeValues(durationColumnIndex).get(1)
- }
- val ID: Long = cursor.getLong(cursor.getColumnIndexOrThrow(ShiftsEntry._ID))
- val currentProductUri: Uri = ContentUris.withAppendedId(ShiftsEntry.CONTENT_URI, ID)
- val b: Bundle = Bundle()
- b.putString("uri", currentProductUri.toString())
- view.setOnClickListener { // activity.clickOnViewItem(ID);
- val fragmentTransaction: FragmentTransaction = (activity.fragmentManager)!!.beginTransaction()
- val fragment2: FurtherInfoFragment = FurtherInfoFragment()
- fragment2.arguments = b
- fragmentTransaction.replace(R.id.container, fragment2).addToBackStack("furtherinfo").commit()
- }
- editView.setOnClickListener {
- val fragmentTransaction: FragmentTransaction = (activity.fragmentManager)!!.beginTransaction()
- val fragment3: FragmentAddItem = FragmentAddItem()
- fragment3.arguments = b
- fragmentTransaction.replace(R.id.container, fragment3).addToBackStack("additem").commit()
- }
- view.setOnLongClickListener {
- println("long click: $ID")
- val builder: AlertDialog.Builder = AlertDialog.Builder(mContext)
- builder.setMessage("Are you sure you want to delete")
- builder.setPositiveButton("delete") { dialog, id -> deleteProduct(ID) }
- builder.setNegativeButton("cancel") { dialog, id ->
- dialog?.dismiss()
- }
- val alertDialog: AlertDialog = builder.create()
- alertDialog.show()
- true
- }
- }
-
- private fun deleteProduct(id: Long) {
- val args: Array = arrayOf(id.toString())
- // String whereClause = String.format(ShiftsEntry._ID + " in (%s)", new Object[] { TextUtils.join(",", Collections.nCopies(args.length, "?")) }); //for deleting multiple lines
- mContext!!.contentResolver.delete(ShiftsEntry.CONTENT_URI, ShiftsEntry._ID + "=?", args)
- }
-
- private fun newDate(dateString: String): String {
- var returnString: String? = "01/01/2010"
- val year: String = dateString.substring(0, 4)
- val month: String = dateString.substring(5, 7)
- val day: String = dateString.substring(8)
- returnString = "$day-$month-$year"
- return returnString
- }
-
- companion object {
- fun timeValues(duration: Float): Array {
- val hours: Int = floor(duration.toDouble()).toInt()
- val minutes: Int = ((duration - hours) * 60).toInt()
- val hoursString: String = hours.toString() + ""
- val minutesString: String = String.format("%02d", minutes)
- return arrayOf(hoursString, minutesString)
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/base/BackPressedListener.kt b/app/src/main/java/com/appttude/h_mal/farmr/base/BackPressedListener.kt
new file mode 100644
index 0000000..3ade301
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/base/BackPressedListener.kt
@@ -0,0 +1,5 @@
+package com.appttude.h_mal.farmr.base
+
+interface BackPressedListener {
+ fun onBackPressed(): Boolean
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/base/BaseActivity.kt b/app/src/main/java/com/appttude/h_mal/farmr/base/BaseActivity.kt
new file mode 100644
index 0000000..16a8384
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/base/BaseActivity.kt
@@ -0,0 +1,58 @@
+package com.appttude.h_mal.farmr.base
+
+import android.content.Intent
+import androidx.activity.ComponentActivity
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.ViewModelLazy
+import com.appttude.h_mal.farmr.utils.displayToast
+import com.appttude.h_mal.farmr.utils.getGenericClassAt
+import com.appttude.h_mal.farmr.viewmodel.ApplicationViewModelFactory
+import org.kodein.di.KodeinAware
+import org.kodein.di.android.kodein
+import org.kodein.di.generic.instance
+
+abstract class BaseActivity : AppCompatActivity(), KodeinAware {
+
+ override val kodein by kodein()
+ private val factory by instance()
+
+ val viewModel: V by getViewModel()
+
+ private fun getViewModel(): Lazy =
+ ViewModelLazy(getGenericClassAt(0), storeProducer = { viewModelStore },
+ factoryProducer = { factory } )
+
+
+ /**
+ * Creates a loading view which to be shown during async operations
+ *
+ * #setOnClickListener(null) is an ugly work around to prevent under being clicked during
+ * loading
+ */
+
+ fun startActivity(activity: Class) {
+ val intent = Intent(this, activity)
+ startActivity(intent)
+ }
+
+ /**
+ * Called in case of success or some data emitted from the liveData in viewModel
+ */
+ open fun onStarted() {}
+
+ /**
+ * Called in case of success or some data emitted from the liveData in viewModel
+ */
+ open fun onSuccess(data: Any?) {}
+
+ /**
+ * Called in case of failure or some error emitted from the liveData in viewModel
+ */
+ open fun onFailure(error: Any?) {
+ if (error is String) displayToast(error)
+ }
+
+ fun setTitleInActionBar(title: String) {
+ setTitle(title)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/base/BaseFragment.kt b/app/src/main/java/com/appttude/h_mal/farmr/base/BaseFragment.kt
new file mode 100644
index 0000000..affb4b0
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/base/BaseFragment.kt
@@ -0,0 +1,79 @@
+package com.appttude.h_mal.farmr.base
+
+import android.os.Bundle
+import android.view.View
+import androidx.annotation.LayoutRes
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.createViewModelLazy
+import com.appttude.h_mal.farmr.model.ViewState
+import com.appttude.h_mal.farmr.utils.getGenericClassAt
+import com.appttude.h_mal.farmr.viewmodel.ApplicationViewModelFactory
+import org.kodein.di.KodeinAware
+import org.kodein.di.android.x.kodein
+import org.kodein.di.generic.instance
+import kotlin.properties.Delegates
+
+@Suppress("EmptyMethod", "EmptyMethod")
+abstract class BaseFragment(@LayoutRes contentLayoutId: Int) :
+ Fragment(contentLayoutId), KodeinAware {
+
+ override val kodein by kodein()
+ private val factory by instance()
+
+ val viewModel: V by getActivityViewModel()
+
+ private fun getActivityViewModel() = createViewModelLazy(
+ getGenericClassAt(0),
+ { requireActivity().viewModelStore },
+ { factory })
+
+ var mActivity: BaseActivity<*>? = null
+
+ private var shortAnimationDuration by Delegates.notNull()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ shortAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ mActivity = requireActivity() as BaseActivity<*>
+ configureObserver()
+ }
+
+ private fun configureObserver() {
+ viewModel.uiState.observe(viewLifecycleOwner) {
+ when (it) {
+ is ViewState.HasStarted -> onStarted()
+ is ViewState.HasData<*> -> onSuccess(it.data)
+ is ViewState.HasError<*> -> onFailure(it.error)
+ }
+ }
+ }
+
+ /**
+ * Called in case of starting operation liveData in viewModel
+ */
+ open fun onStarted() {
+ mActivity?.onStarted()
+ }
+
+ /**
+ * Called in case of success or some data emitted from the liveData in viewModel
+ */
+ open fun onSuccess(data: Any?) {
+ mActivity?.onSuccess(data)
+ }
+
+ /**
+ * Called in case of failure or some error emitted from the liveData in viewModel
+ */
+ open fun onFailure(error: Any?) {
+ mActivity?.onFailure(error)
+ }
+
+ fun setTitle(title: String) {
+ (requireActivity() as BaseActivity<*>).setTitleInActionBar(title)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/base/BaseRecyclerAdapter.kt b/app/src/main/java/com/appttude/h_mal/farmr/base/BaseRecyclerAdapter.kt
new file mode 100644
index 0000000..ab62a76
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/base/BaseRecyclerAdapter.kt
@@ -0,0 +1,47 @@
+package com.appttude.h_mal.farmr.base
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.appttude.h_mal.farmr.utils.generateView
+
+open class BaseRecyclerAdapter(
+ @LayoutRes private val emptyViewId: Int,
+ @LayoutRes private val currentViewId: Int
+): RecyclerView.Adapter() {
+ var list: List? = null
+
+ fun updateData(newList: List) {
+ list = newList
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return if (list.isNullOrEmpty()) {
+ val emptyViewHolder = parent.generateView(emptyViewId)
+ EmptyViewHolder(emptyViewHolder)
+ } else {
+ val currentViewHolder = parent.generateView(currentViewId)
+ CurrentViewHolder(currentViewHolder)
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return if (list.isNullOrEmpty()) 1 else list!!.size
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ when (holder) {
+ is EmptyViewHolder -> bindEmptyView(holder.itemView)
+ is CurrentViewHolder -> bindCurrentView(holder.itemView, position, list!![position])
+ }
+ }
+
+ open fun bindEmptyView(view: View) {}
+ open fun bindCurrentView(view: View, position: Int, data: T) {}
+
+ class EmptyViewHolder(itemView: View): ViewHolder(itemView)
+ class CurrentViewHolder(itemView: View): ViewHolder(itemView)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/base/BaseViewModel.kt b/app/src/main/java/com/appttude/h_mal/farmr/base/BaseViewModel.kt
new file mode 100644
index 0000000..40c0b5c
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/base/BaseViewModel.kt
@@ -0,0 +1,26 @@
+package com.appttude.h_mal.farmr.base
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.appttude.h_mal.farmr.model.ViewState
+import java.lang.Exception
+
+open class BaseViewModel: ViewModel() {
+
+ private val _uiState = MutableLiveData()
+ val uiState: LiveData = _uiState
+
+
+ fun onStart() {
+ _uiState.postValue(ViewState.HasStarted)
+ }
+
+ fun onSuccess(result: T) {
+ _uiState.postValue(ViewState.HasData(result))
+ }
+
+ protected fun onError(error: E) {
+ _uiState.postValue(ViewState.HasError(error))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/data/Repository.kt b/app/src/main/java/com/appttude/h_mal/farmr/data/Repository.kt
new file mode 100644
index 0000000..db97694
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/data/Repository.kt
@@ -0,0 +1,24 @@
+package com.appttude.h_mal.farmr.data
+
+import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
+import com.appttude.h_mal.farmr.model.Order
+import com.appttude.h_mal.farmr.model.Shift
+import com.appttude.h_mal.farmr.model.Sortable
+
+interface Repository {
+ fun insertShiftIntoDatabase(shift: Shift): Boolean
+ fun updateShiftIntoDatabase(id: Long, shift: Shift): Boolean
+ fun readShiftsFromDatabase(): List?
+ fun readSingleShiftFromDatabase(id: Long): ShiftObject?
+ fun deleteSingleShiftFromDatabase(id: Long): Boolean
+ fun deleteAllShiftsFromDatabase(): Boolean
+ fun retrieveSortAndOrderFromPref(): Pair
+ fun setSortAndOrderFromPref(sortable: Sortable, order: Order)
+ fun retrieveFilteringDetailsInPrefs(): Map
+ fun setFilteringDetailsInPrefs(
+ description: String?,
+ timeIn: String?,
+ timeOut: String?,
+ type: String?
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/data/RepositoryImpl.kt b/app/src/main/java/com/appttude/h_mal/farmr/data/RepositoryImpl.kt
new file mode 100644
index 0000000..3af7652
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/data/RepositoryImpl.kt
@@ -0,0 +1,71 @@
+package com.appttude.h_mal.farmr.data
+
+import com.appttude.h_mal.farmr.data.legacydb.LegacyDatabase
+import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
+import com.appttude.h_mal.farmr.data.prefs.PreferenceProvider
+import com.appttude.h_mal.farmr.model.Order
+import com.appttude.h_mal.farmr.model.Shift
+import com.appttude.h_mal.farmr.model.Sortable
+
+class RepositoryImpl(
+ private val legacyDatabase: LegacyDatabase,
+ private val preferenceProvider: PreferenceProvider
+): Repository {
+ override fun insertShiftIntoDatabase(shift: Shift): Boolean {
+ return legacyDatabase.insertShiftDataIntoDatabase(shift) != null
+ }
+
+ override fun updateShiftIntoDatabase(id: Long, shift: Shift): Boolean {
+ return legacyDatabase.updateShiftDataIntoDatabase(
+ id = id,
+ typeString = shift.type.type,
+ descriptionString = shift.description,
+ dateString = shift.date,
+ timeInString = shift.timeIn ?: "",
+ timeOutString = shift.timeOut ?: "",
+ duration = shift.duration ?: 0f,
+ breaks = shift.breakMins ?: 0,
+ units = shift.units ?: 0f,
+ payRate = shift.rateOfPay,
+ totalPay = shift.totalPay
+ ) == 1
+ }
+
+ override fun readShiftsFromDatabase(): List? {
+ return legacyDatabase.readShiftsFromDatabase()
+ }
+
+ override fun readSingleShiftFromDatabase(id: Long): ShiftObject? {
+ return legacyDatabase.readSingleShiftWithId(id)
+ }
+
+ override fun deleteSingleShiftFromDatabase(id: Long): Boolean {
+ return legacyDatabase.deleteSingleShift(id) == 1
+ }
+
+ override fun deleteAllShiftsFromDatabase(): Boolean {
+ return legacyDatabase.deleteAllShiftsInDatabase() > 0
+ }
+
+ override fun retrieveSortAndOrderFromPref(): Pair {
+ return preferenceProvider.getSortableAndOrder()
+ }
+
+ override fun setSortAndOrderFromPref(sortable: Sortable, order: Order) {
+ preferenceProvider.saveSortableAndOrder(sortable, order)
+ }
+
+ override fun retrieveFilteringDetailsInPrefs(): Map {
+ return preferenceProvider.getFilteringDetails()
+ }
+
+ override fun setFilteringDetailsInPrefs(
+ description: String?,
+ timeIn: String?,
+ timeOut: String?,
+ type: String?
+ ) {
+ preferenceProvider.saveFilteringDetails(description, timeIn, timeOut, type)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/LegacyDatabase.kt b/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/LegacyDatabase.kt
new file mode 100644
index 0000000..c413f9f
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/LegacyDatabase.kt
@@ -0,0 +1,167 @@
+package com.appttude.h_mal.farmr.data.legacydb
+
+import android.content.ContentUris
+import android.content.ContentValues
+import android.content.Context
+import android.database.Cursor
+import android.net.Uri
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_BREAK
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DATE
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DESCRIPTION
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DURATION
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_PAYRATE
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_IN
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_OUT
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TOTALPAY
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TYPE
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_UNIT
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.CONTENT_URI
+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.ShiftType
+
+class LegacyDatabase(context: Context) {
+ private val resolver = context.contentResolver
+
+ private val projection = arrayOf(
+ _ID,
+ COLUMN_SHIFT_DESCRIPTION,
+ COLUMN_SHIFT_DATE,
+ COLUMN_SHIFT_TIME_IN,
+ COLUMN_SHIFT_TIME_OUT,
+ COLUMN_SHIFT_BREAK,
+ COLUMN_SHIFT_DURATION,
+ COLUMN_SHIFT_TYPE,
+ COLUMN_SHIFT_UNIT,
+ COLUMN_SHIFT_PAYRATE,
+ COLUMN_SHIFT_TOTALPAY
+ )
+
+ // Create
+ fun insertShiftDataIntoDatabase(
+ shift: Shift
+ ): Uri? {
+ val values = ContentValues().apply {
+ put(COLUMN_SHIFT_TYPE, shift.type.type)
+ put(COLUMN_SHIFT_DESCRIPTION, shift.description)
+ put(COLUMN_SHIFT_DATE, shift.description)
+ put(COLUMN_SHIFT_TIME_IN, shift.timeIn ?: "00:00")
+ put(COLUMN_SHIFT_TIME_OUT, shift.timeOut ?: "00:00")
+ put(COLUMN_SHIFT_DURATION, shift.duration ?: 0.00f)
+ put(COLUMN_SHIFT_BREAK, shift.breakMins ?: 0)
+ put(COLUMN_SHIFT_UNIT, shift.units ?: 0.00f)
+ put(COLUMN_SHIFT_PAYRATE, shift.rateOfPay)
+ put(COLUMN_SHIFT_TOTALPAY, shift.totalPay)
+ }
+ return resolver.insert(CONTENT_URI, values)
+ }
+
+ // Read
+ fun readShiftsFromDatabase(): List? {
+ val cursor = resolver.query(
+ CONTENT_URI,
+ projection,
+ null, null, null
+ ) ?: return null
+ cursor.moveToFirst()
+ val shifts = (0..cursor.count).map { cursor.getShift() }
+ // close cursor after query operations
+ cursor.close()
+
+ return shifts
+ }
+
+ fun readSingleShiftWithId(id: Long): ShiftObject? {
+ val itemUri: Uri = ContentUris.withAppendedId(CONTENT_URI, id)
+
+ val cursor = resolver.query(
+ itemUri,
+ projection,
+ null, null, null
+ ) ?: return null
+ cursor.moveToFirst()
+
+ val shift = cursor.takeIf { it.moveToFirst() }?.run { getShift() } ?: return null
+ cursor.close()
+ return shift
+ }
+
+ // Update
+ fun updateShiftDataIntoDatabase(
+ id: Long,
+ typeString: String,
+ descriptionString: String,
+ dateString: String,
+ timeInString: String,
+ timeOutString: String,
+ duration: Float,
+ breaks: Int,
+ units: Float,
+ payRate: Float,
+ totalPay: Float,
+ ): Int {
+ val itemUri: Uri = ContentUris.withAppendedId(CONTENT_URI, id)
+
+ val values = ContentValues().apply {
+ put(COLUMN_SHIFT_TYPE, typeString)
+ put(COLUMN_SHIFT_DESCRIPTION, descriptionString)
+ put(COLUMN_SHIFT_DATE, dateString)
+ put(COLUMN_SHIFT_TIME_IN, timeInString)
+ put(COLUMN_SHIFT_TIME_OUT, timeOutString)
+ put(COLUMN_SHIFT_DURATION, duration)
+ put(COLUMN_SHIFT_BREAK, breaks)
+ put(COLUMN_SHIFT_UNIT, units)
+ put(COLUMN_SHIFT_PAYRATE, payRate)
+ put(COLUMN_SHIFT_TOTALPAY, totalPay)
+ }
+ return resolver.update(itemUri, values, null, null)
+ }
+
+ // Delete
+ fun deleteAllShiftsInDatabase(): Int {
+ return resolver.delete(CONTENT_URI, null, null)
+ }
+
+ fun deleteSingleShift(id: Long): Int {
+ val args: Array = arrayOf(id.toString())
+ return resolver.delete(CONTENT_URI, "$_ID=?", args)
+ }
+
+ private fun Cursor.getShift(): ShiftObject = run {
+ val id = getLong(getColumnIndexOrThrow(_ID))
+ val descriptionColumnIndex = getString(
+ getColumnIndexOrThrow(
+ COLUMN_SHIFT_DESCRIPTION
+ )
+ )
+ val dateColumnIndex = getString(getColumnIndexOrThrow(COLUMN_SHIFT_DATE))
+ val timeInColumnIndex =
+ getString(getColumnIndexOrThrow(COLUMN_SHIFT_TIME_IN))
+ val timeOutColumnIndex =
+ getString(getColumnIndexOrThrow(COLUMN_SHIFT_TIME_OUT))
+ val durationColumnIndex =
+ getFloat(getColumnIndexOrThrow(COLUMN_SHIFT_DURATION))
+ val breakOutColumnIndex =
+ getInt(getColumnIndexOrThrow(COLUMN_SHIFT_BREAK))
+ val typeColumnIndex = getString(getColumnIndexOrThrow(COLUMN_SHIFT_TYPE))
+ val unitColumnIndex = getFloat(getColumnIndexOrThrow(COLUMN_SHIFT_UNIT))
+ val payrateColumnIndex =
+ getFloat(getColumnIndexOrThrow(COLUMN_SHIFT_PAYRATE))
+ val totalpayColumnIndex =
+ getFloat(getColumnIndexOrThrow(COLUMN_SHIFT_TOTALPAY))
+
+ ShiftObject(
+ id = id,
+ type = typeColumnIndex,
+ description = descriptionColumnIndex,
+ date = dateColumnIndex,
+ timeIn = timeInColumnIndex,
+ timeOut = timeOutColumnIndex,
+ duration = durationColumnIndex,
+ breakMins = breakOutColumnIndex,
+ units = unitColumnIndex,
+ rateOfPay = payrateColumnIndex,
+ totalPay = totalpayColumnIndex
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftObject.kt b/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftObject.kt
new file mode 100644
index 0000000..af40553
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftObject.kt
@@ -0,0 +1,28 @@
+package com.appttude.h_mal.farmr.data.legacydb
+
+import com.appttude.h_mal.farmr.model.Shift
+import com.appttude.h_mal.farmr.model.ShiftType
+import kotlin.math.floor
+
+data class ShiftObject(
+ val id: Long,
+ val type: String,
+ val description: String,
+ val date: String,
+ val timeIn: String,
+ val timeOut: String,
+ val duration: Float,
+ val breakMins: Int,
+ val units: Float,
+ val rateOfPay: Float,
+ val totalPay: Float
+) {
+ fun copyToShift() = Shift(ShiftType.getEnumByType(type), description, date, timeIn, timeOut, duration, breakMins, units, rateOfPay, totalPay)
+
+ fun getHoursMinutesPairFromDuration(): Pair {
+ val hours: Int = floor(duration).toInt()
+ val minutes: Int = ((duration - hours) * 60).toInt()
+ return Pair(hours.toString(), minutes.toString())
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/data/ShiftProvider.kt b/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftProvider.kt
similarity index 97%
rename from app/src/main/java/com/appttude/h_mal/farmr/data/ShiftProvider.kt
rename to app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftProvider.kt
index d6cc534..87ad3d8 100644
--- a/app/src/main/java/com/appttude/h_mal/farmr/data/ShiftProvider.kt
+++ b/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftProvider.kt
@@ -1,15 +1,13 @@
-package com.appttude.h_mal.farmr.data
+package com.appttude.h_mal.farmr.data.legacydb
import android.content.ContentProvider
import android.content.ContentUris
import android.content.ContentValues
-import android.content.Context
import android.content.UriMatcher
import android.database.Cursor
import android.net.Uri
import android.util.Log
-import androidx.annotation.VisibleForTesting
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry
/**
* Created by h_mal on 26/12/2017.
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/data/ShiftsContract.kt b/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftsContract.kt
similarity index 96%
rename from app/src/main/java/com/appttude/h_mal/farmr/data/ShiftsContract.kt
rename to app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftsContract.kt
index ac1a5d0..9fa0a96 100644
--- a/app/src/main/java/com/appttude/h_mal/farmr/data/ShiftsContract.kt
+++ b/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftsContract.kt
@@ -1,4 +1,4 @@
-package com.appttude.h_mal.farmr.data
+package com.appttude.h_mal.farmr.data.legacydb
import android.content.ContentResolver
import android.net.Uri
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/data/ShiftsDbHelper.kt b/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftsDbHelper.kt
similarity index 95%
rename from app/src/main/java/com/appttude/h_mal/farmr/data/ShiftsDbHelper.kt
rename to app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftsDbHelper.kt
index fe83ed6..4918518 100644
--- a/app/src/main/java/com/appttude/h_mal/farmr/data/ShiftsDbHelper.kt
+++ b/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftsDbHelper.kt
@@ -1,9 +1,9 @@
-package com.appttude.h_mal.farmr.data
+package com.appttude.h_mal.farmr.data.legacydb
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
-import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
+import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry
/**
* Created by h_mal on 26/12/2017.
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/data/prefs/PreferencesProvider.kt b/app/src/main/java/com/appttude/h_mal/farmr/data/prefs/PreferencesProvider.kt
new file mode 100644
index 0000000..99004a6
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/data/prefs/PreferencesProvider.kt
@@ -0,0 +1,65 @@
+package com.appttude.h_mal.farmr.data.prefs
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.preference.PreferenceManager
+import com.appttude.h_mal.farmr.model.Order
+import com.appttude.h_mal.farmr.model.Sortable
+
+/**
+ * Shared preferences to save & load last timestamp
+ */
+const val SORT = "SORT"
+const val ORDER = "ORDER"
+
+const val DESCRIPTION = "DESCRIPTION"
+const val TIME_IN = "TIME_IN"
+const val TIME_OUT = "TIME_OUT"
+const val TYPE = "TYPE"
+
+class PreferenceProvider(
+ context: Context
+) {
+
+ private val appContext = context.applicationContext
+
+ private val preference: SharedPreferences
+ get() = PreferenceManager.getDefaultSharedPreferences(appContext)
+
+ fun saveSortableAndOrder(sortable: Sortable, order: Order) {
+ preference.edit()
+ .putString(SORT, sortable.label)
+ .putString(ORDER, order.label)
+ .apply()
+ }
+
+ fun getSortableAndOrder(): Pair {
+ val sort = preference.getString(SORT, null)?.let { Sortable.valueOf(it) }
+ val order = preference.getString(ORDER, null)?.let { Order.valueOf(it) }
+
+ return Pair(sort, order)
+ }
+ fun saveFilteringDetails(
+ description: String?,
+ timeIn: String?,
+ timeOut: String?,
+ type: String?
+ ) {
+ preference.edit()
+ .putString(DESCRIPTION, description)
+ .putString(TIME_IN, timeIn)
+ .putString(TIME_OUT, timeOut)
+ .putString(TYPE, type)
+ .apply()
+ }
+
+ fun getFilteringDetails(): Map {
+ return mapOf(
+ Pair(DESCRIPTION, preference.getString(DESCRIPTION, null)),
+ Pair(TIME_IN, preference.getString(TIME_IN, null)),
+ Pair(TIME_OUT, preference.getString(TIME_OUT, null)),
+ Pair(TYPE, preference.getString(TYPE, null))
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/di/ShiftApplication.kt b/app/src/main/java/com/appttude/h_mal/farmr/di/ShiftApplication.kt
new file mode 100644
index 0000000..ced4fb4
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/di/ShiftApplication.kt
@@ -0,0 +1,28 @@
+package com.appttude.h_mal.farmr.di
+
+import android.app.Application
+import com.appttude.h_mal.farmr.data.RepositoryImpl
+import com.appttude.h_mal.farmr.data.legacydb.LegacyDatabase
+import com.appttude.h_mal.farmr.data.prefs.PreferenceProvider
+import com.appttude.h_mal.farmr.viewmodel.ApplicationViewModelFactory
+import com.appttude.h_mal.farmr.viewmodel.MainViewModel
+import org.kodein.di.Kodein
+import org.kodein.di.KodeinAware
+import org.kodein.di.android.x.androidXModule
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.instance
+import org.kodein.di.generic.provider
+import org.kodein.di.generic.singleton
+
+class ShiftApplication: Application(), KodeinAware {
+ // Kodein creation of modules to be retrieve within the app
+ override val kodein = Kodein.lazy {
+ import(androidXModule(this@ShiftApplication))
+
+ bind() from singleton { LegacyDatabase(this@ShiftApplication) }
+ bind() from singleton { PreferenceProvider(this@ShiftApplication) }
+ bind() from singleton { RepositoryImpl(instance(), instance()) }
+
+ bind() from provider { ApplicationViewModelFactory(instance()) }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/model/DatabaseShift.kt b/app/src/main/java/com/appttude/h_mal/farmr/model/DatabaseShift.kt
new file mode 100644
index 0000000..4120cdc
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/model/DatabaseShift.kt
@@ -0,0 +1,11 @@
+package com.appttude.h_mal.farmr.model
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+data class EntityItem(
+ @PrimaryKey(autoGenerate = false)
+ val id: String,
+ val shift: Shift
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/model/FilterStore.kt b/app/src/main/java/com/appttude/h_mal/farmr/model/FilterStore.kt
new file mode 100644
index 0000000..45a3e87
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/model/FilterStore.kt
@@ -0,0 +1,8 @@
+package com.appttude.h_mal.farmr.model
+
+data class FilterStore(
+ val description: String?,
+ val dateFrom: String?,
+ val dateTo: String?,
+ val type: String?
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/model/Order.kt b/app/src/main/java/com/appttude/h_mal/farmr/model/Order.kt
new file mode 100644
index 0000000..b7c971f
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/model/Order.kt
@@ -0,0 +1,5 @@
+package com.appttude.h_mal.farmr.model
+
+enum class Order(val label: String) {
+ ASCENDING("Ascending"), DESCENDING("Descending")
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/model/Shift.kt b/app/src/main/java/com/appttude/h_mal/farmr/model/Shift.kt
index 5d76543..3d8e5ab 100644
--- a/app/src/main/java/com/appttude/h_mal/farmr/model/Shift.kt
+++ b/app/src/main/java/com/appttude/h_mal/farmr/model/Shift.kt
@@ -1,14 +1,68 @@
package com.appttude.h_mal.farmr.model
+import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
+import com.appttude.h_mal.farmr.utils.calculateDuration
+import com.appttude.h_mal.farmr.utils.convertTimeStringToHourMinutesPair
+import com.appttude.h_mal.farmr.utils.formatToTwoDp
+import java.io.IOException
+
data class Shift(
- val type: ShiftType,
- val description: String,
- val date: String,
- val timeIn: String?,
- val timeOut: String?,
- val duration: Float?,
- val breakMins: Int?,
- val units: Float?,
- val rateOfPay: Float,
- val totalPay: Float
-)
\ No newline at end of file
+ val type: ShiftType,
+ val description: String,
+ val date: String,
+ val timeIn: String?,
+ val timeOut: String?,
+ val duration: Float?,
+ val breakMins: Int?,
+ val units: Float?,
+ val rateOfPay: Float,
+ val totalPay: Float
+) {
+ companion object {
+ // Invocation for Hourly
+ operator fun invoke(
+ description: String,
+ date: String,
+ timeIn: String,
+ timeOut: String,
+ breakMins: Int? = null,
+ rateOfPay: Float
+ ): Shift {
+ val breakTime = breakMins ?: 0
+ val duration = calculateDuration(timeIn, timeOut, breakTime)
+
+ return Shift(
+ ShiftType.HOURLY,
+ description,
+ date,
+ timeIn,
+ timeOut,
+ duration,
+ breakTime,
+ duration,
+ rateOfPay,
+ (duration * rateOfPay).formatToTwoDp()
+ )
+ }
+
+ operator fun invoke(
+ description: String,
+ date: String,
+ units: Float,
+ rateOfPay: Float
+ ) = Shift(
+ ShiftType.PIECE,
+ description,
+ date,
+ "",
+ "",
+ 0f,
+ 0,
+ units,
+ rateOfPay,
+ (units * rateOfPay).formatToTwoDp()
+ )
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/model/ShiftType.kt b/app/src/main/java/com/appttude/h_mal/farmr/model/ShiftType.kt
index d4d1118..b724c19 100644
--- a/app/src/main/java/com/appttude/h_mal/farmr/model/ShiftType.kt
+++ b/app/src/main/java/com/appttude/h_mal/farmr/model/ShiftType.kt
@@ -1,6 +1,16 @@
package com.appttude.h_mal.farmr.model
-enum class ShiftType(val type: String) {
+enum class ShiftType(val type: String){
HOURLY("Hourly"),
- PIECE("Piece Rate")
+ PIECE("Piece Rate");
+
+ fun getEnumByType(type: String): ShiftType {
+ return values().first { it.type == type }
+ }
+
+ companion object {
+ fun getEnumByType(type: String): ShiftType {
+ return values().first { it.type == type }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/model/Sortable.kt b/app/src/main/java/com/appttude/h_mal/farmr/model/Sortable.kt
new file mode 100644
index 0000000..6a0c9a1
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/model/Sortable.kt
@@ -0,0 +1,11 @@
+package com.appttude.h_mal.farmr.model
+
+enum class Sortable(val label: String) {
+ ID("Default"),
+ TYPE("Shift Type"),
+ DATE("Date"),
+ DESCRIPTION("Description"),
+ DURATION("Added"), UNITS("Duration"),
+ RATEOFPAY("Rate of pay"),
+ TOTALPAY("Total Pay")
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/model/ViewState.kt b/app/src/main/java/com/appttude/h_mal/farmr/model/ViewState.kt
new file mode 100644
index 0000000..2080f55
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/model/ViewState.kt
@@ -0,0 +1,7 @@
+package com.appttude.h_mal.farmr.model
+
+sealed class ViewState {
+ object HasStarted : ViewState()
+ class HasData(val data: T) : ViewState()
+ class HasError(val error: T) : ViewState()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/ui/FilterDataFragment.kt b/app/src/main/java/com/appttude/h_mal/farmr/ui/FilterDataFragment.kt
new file mode 100644
index 0000000..ca917f3
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/ui/FilterDataFragment.kt
@@ -0,0 +1,92 @@
+package com.appttude.h_mal.farmr.ui
+
+import android.os.Bundle
+import android.view.View
+import android.view.View.OnClickListener
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Button
+import android.widget.EditText
+import android.widget.Spinner
+import androidx.core.widget.doAfterTextChanged
+import com.appttude.h_mal.farmr.R
+import com.appttude.h_mal.farmr.base.BaseFragment
+import com.appttude.h_mal.farmr.model.ShiftType
+import com.appttude.h_mal.farmr.utils.setDatePicker
+import com.appttude.h_mal.farmr.viewmodel.MainViewModel
+
+class FilterDataFragment : BaseFragment(R.layout.fragment_filter_data),
+ AdapterView.OnItemSelectedListener, OnClickListener {
+ private val spinnerList: Array =
+ arrayOf("", ShiftType.HOURLY.type, ShiftType.PIECE.type)
+
+ private lateinit var LocationET: EditText
+ private lateinit var dateFromET: EditText
+ private lateinit var dateToET: EditText
+ private lateinit var typeSpinner: Spinner
+
+ private var description: String? = null
+ private var dateFrom: String? = null
+ private var dateTo: String? = null
+ private var type: String? = null
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setTitle(getString(R.string.title_activity_filter_data))
+
+ LocationET = view.findViewById(R.id.filterLocationEditText)
+ dateFromET = view.findViewById(R.id.fromdateInEditText)
+ dateToET = view.findViewById(R.id.filterDateOutEditText)
+ typeSpinner = view.findViewById(R.id.TypeFilterEditText)
+ val submit: Button = view.findViewById(R.id.submitFiltered)
+
+ val adapter: ArrayAdapter =
+ ArrayAdapter((context)!!, android.R.layout.simple_spinner_dropdown_item, spinnerList)
+ typeSpinner.adapter = adapter
+
+ val filterDetails = viewModel.getFiltrationDetails()
+
+ filterDetails.let {
+ LocationET.setText(it.description)
+ dateFromET.setText(it.dateFrom)
+ dateToET.setText(it.dateTo)
+
+ it.type?.let { t ->
+ val spinnerPosition: Int = adapter.getPosition(t)
+ typeSpinner.setSelection(spinnerPosition)
+ }
+
+ }
+
+ LocationET.doAfterTextChanged { description = it.toString() }
+ dateFromET.setDatePicker { dateFrom = it }
+ dateToET.setDatePicker { dateTo = it }
+ typeSpinner.onItemSelectedListener = this
+
+ submit.setOnClickListener(this)
+ }
+
+ override fun onItemSelected(
+ parentView: AdapterView<*>?,
+ selectedItemView: View?,
+ position: Int,
+ id: Long
+ ) {
+ type = when (position) {
+ 1 -> ShiftType.HOURLY.toString()
+ 2 -> ShiftType.PIECE.toString()
+ else -> return
+ }
+ }
+
+ override fun onNothingSelected(parentView: AdapterView<*>?) {}
+
+ private fun submitFiltrationDetails() {
+ viewModel.setFiltrationDetails(description, dateFrom, dateTo, type)
+ }
+
+ override fun onClick(p0: View?) {
+ submitFiltrationDetails()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentAddItem.kt b/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentAddItem.kt
new file mode 100644
index 0000000..f201bf1
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentAddItem.kt
@@ -0,0 +1,229 @@
+package com.appttude.h_mal.farmr.ui
+
+import android.os.Bundle
+import android.view.View
+import android.widget.Button
+import android.widget.EditText
+import android.widget.LinearLayout
+import android.widget.RadioButton
+import android.widget.RadioGroup
+import android.widget.ScrollView
+import android.widget.TextView
+import androidx.core.widget.doAfterTextChanged
+import com.appttude.h_mal.farmr.R
+import com.appttude.h_mal.farmr.base.BackPressedListener
+import com.appttude.h_mal.farmr.base.BaseFragment
+import com.appttude.h_mal.farmr.model.ShiftType
+import com.appttude.h_mal.farmr.utils.ID
+import com.appttude.h_mal.farmr.utils.createDialog
+import com.appttude.h_mal.farmr.utils.formatToTwoDpString
+import com.appttude.h_mal.farmr.utils.hide
+import com.appttude.h_mal.farmr.utils.popBackStack
+import com.appttude.h_mal.farmr.utils.setDatePicker
+import com.appttude.h_mal.farmr.utils.setTimePicker
+import com.appttude.h_mal.farmr.utils.show
+import com.appttude.h_mal.farmr.utils.validateField
+import com.appttude.h_mal.farmr.viewmodel.MainViewModel
+
+class FragmentAddItem : BaseFragment(R.layout.fragment_add_item),
+ RadioGroup.OnCheckedChangeListener, BackPressedListener {
+
+ private lateinit var mHourlyRadioButton: RadioButton
+ private lateinit var mPieceRadioButton: RadioButton
+ private lateinit var mLocationEditText: EditText
+ private lateinit var mDateEditText: EditText
+ private lateinit var mDurationTextView: TextView
+ private lateinit var mTimeInEditText: EditText
+ private lateinit var mTimeOutEditText: EditText
+ private lateinit var mBreakEditText: EditText
+ private lateinit var mUnitEditText: EditText
+ private lateinit var mPayRateEditText: EditText
+ private lateinit var mTotalPayTextView: TextView
+ private lateinit var hourlyDataView: LinearLayout
+ private lateinit var unitsHolder: LinearLayout
+ private lateinit var durationHolder: LinearLayout
+ private lateinit var wholeView: LinearLayout
+ private lateinit var scrollView: ScrollView
+ private lateinit var submitProduct: Button
+ private lateinit var mRadioGroup: RadioGroup
+
+ private var mDate: String? = null
+ private var mDescription: String? = null
+ private var mTimeIn: String? = null
+ private var mTimeOut: String? = null
+ private var mBreaks = 0
+ private var mUnits = 0f
+ private var mPayRate = 0f
+ private var mType: ShiftType? = null
+ private var mDuration: Float = 0f
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ scrollView = view.findViewById(R.id.total_view)
+ mRadioGroup = view.findViewById(R.id.rg)
+ mHourlyRadioButton = view.findViewById(R.id.hourly)
+ mPieceRadioButton = view.findViewById(R.id.piecerate)
+ mLocationEditText = view.findViewById(R.id.locationEditText)
+ mDateEditText = view.findViewById(R.id.dateEditText)
+ mTimeInEditText = view.findViewById(R.id.timeInEditText)
+ mBreakEditText = view.findViewById(R.id.breakEditText)
+ mTimeOutEditText = view.findViewById(R.id.timeOutEditText)
+ mDurationTextView = view.findViewById(R.id.ShiftDuration)
+ mUnitEditText = view.findViewById(R.id.unitET)
+ mPayRateEditText = view.findViewById(R.id.payrateET)
+ mTotalPayTextView = view.findViewById(R.id.totalpayval)
+ hourlyDataView = view.findViewById(R.id.hourly_data_holder)
+ unitsHolder = view.findViewById(R.id.units_holder)
+ durationHolder = view.findViewById(R.id.duration_holder)
+ wholeView = view.findViewById(R.id.whole_view)
+ submitProduct = view.findViewById(R.id.submit)
+
+ mRadioGroup.setOnCheckedChangeListener(this)
+ mLocationEditText.doAfterTextChanged {
+ mDescription = it.toString()
+ }
+ mDateEditText.setDatePicker { mDate = it }
+ mTimeInEditText.setTimePicker {
+ mTimeIn = it
+ calculateTotalPay()
+ }
+ mTimeOutEditText.setTimePicker {
+ mTimeOut = it
+ calculateTotalPay()
+ }
+ mBreakEditText.doAfterTextChanged {
+ mBreaks = it.toString().toIntOrNull() ?: 0
+ calculateTotalPay()
+ }
+ mUnitEditText.doAfterTextChanged {
+ it.toString().toFloatOrNull()?.let { u -> mPayRate = u }
+ calculateTotalPay()
+ }
+ mPayRateEditText.doAfterTextChanged {
+ it.toString().toFloatOrNull()?.let { p ->
+ mPayRate = p
+ calculateTotalPay()
+ }
+ }
+
+ submitProduct.setOnClickListener { submitShift() }
+
+ setupViewAfterViewCreated()
+ }
+
+ private fun setupViewAfterViewCreated() {
+ val title = when (arguments?.containsKey(ID)) {
+ true -> {
+ // Since we are editing a shift lets load the shift data into the views
+ viewModel.getCurrentShift(arguments!!.getLong(ID))?.run {
+ mLocationEditText.setText(description)
+ mDateEditText.setText(date)
+ when (ShiftType.getEnumByType(type)) {
+ ShiftType.HOURLY -> {
+ mHourlyRadioButton.isChecked = true
+ mPieceRadioButton.isChecked = false
+ mTimeInEditText.setText(timeIn)
+ mTimeOutEditText.setText(timeOut)
+ mBreakEditText.setText(breakMins.toString())
+ val durationText = "${duration.formatToTwoDpString()} Hours"
+ mDurationTextView.text = durationText
+ }
+
+ ShiftType.PIECE -> {
+ mHourlyRadioButton.isChecked = false
+ mPieceRadioButton.isChecked = true
+ mUnitEditText.setText(units.formatToTwoDpString())
+ }
+ }
+ mPayRateEditText.setText(rateOfPay.formatToTwoDpString())
+ mTotalPayTextView.text = totalPay.formatToTwoDpString()
+ }
+
+ // Return title
+ getString(R.string.edit_item_title)
+ }
+
+ else -> getString(R.string.add_item_title)
+ }
+ setTitle(title)
+ }
+
+ override fun onCheckedChanged(radioGroup: RadioGroup, id: Int) {
+ when (radioGroup.checkedRadioButtonId) {
+ R.id.hourly -> {
+ mType = ShiftType.HOURLY
+ wholeView.show()
+ unitsHolder.hide()
+ hourlyDataView.show()
+ durationHolder.show()
+ }
+ R.id.piecerate -> {
+ mType = ShiftType.PIECE
+ wholeView.show()
+ unitsHolder.show()
+ hourlyDataView.hide()
+ durationHolder.hide()
+ }
+ }
+ }
+
+ private fun submitShift() {
+ mDate.validateField({ !it.isNullOrBlank() }){
+ onFailure("Date field cannot be empty")
+ return
+ }
+ mDescription.validateField({ !it.isNullOrBlank() }){
+ onFailure("Description field cannot be empty")
+ return
+ }
+ mPayRate.validateField({ !it.isNaN() }){
+ onFailure("Rate of pay field cannot be empty")
+ return
+ }
+
+ if (mPieceRadioButton.isChecked) {
+ mUnits.validateField({!it.isNaN()}) {
+ onFailure("Units field cannot be empty")
+ return
+ }
+ viewModel.insertPieceRateShift(mDescription!!, mDate!!, mUnits, mPayRate)
+ } else if (mHourlyRadioButton.isChecked) {
+ viewModel.insertHourlyShift(mDescription!!, mDate!!, mPayRate, mTimeIn, mTimeOut, mBreaks)
+ }
+ }
+
+ private fun calculateTotalPay() {
+ mType?.let {
+ val total = when (it) {
+ ShiftType.HOURLY -> {
+ // Calculate duration before total pay calculation
+ mDuration = viewModel.retrieveDurationText(mTimeIn, mTimeOut, mBreaks) ?: return
+ mDurationTextView.text = StringBuilder().append(mDuration).append(" hours").toString()
+ mDuration * mPayRate
+ }
+ ShiftType.PIECE -> {
+ mUnits * mPayRate
+ }
+ }
+ mTotalPayTextView.text = total.formatToTwoDpString()
+ }
+ }
+
+ override fun onBackPressed(): Boolean {
+ if (mRadioGroup.checkedRadioButtonId == -1) {
+ mActivity?.popBackStack()
+ } else {
+ requireContext().createDialog(
+ title = "Discard Changes?",
+ message = "Are you sure you want to discard changes?",
+ displayCancel = true,
+ okCallback = { _, _ ->
+ mActivity?.popBackStack()
+ }
+ )
+ }
+ return true
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentMain.kt b/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentMain.kt
new file mode 100644
index 0000000..6d053d6
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentMain.kt
@@ -0,0 +1,367 @@
+package com.appttude.h_mal.farmr.ui
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.AlertDialog
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.view.MenuItem
+import android.view.View
+import android.widget.Toast
+import androidx.core.app.ActivityCompat
+import androidx.recyclerview.widget.RecyclerView
+import com.appttude.h_mal.farmr.R
+import com.appttude.h_mal.farmr.base.BackPressedListener
+import com.appttude.h_mal.farmr.base.BaseFragment
+import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
+import com.appttude.h_mal.farmr.model.Order
+import com.appttude.h_mal.farmr.model.Sortable
+import com.appttude.h_mal.farmr.utils.createDialog
+import com.appttude.h_mal.farmr.utils.navigateToFragment
+import com.appttude.h_mal.farmr.viewmodel.MainViewModel
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import kotlin.system.exitProcess
+
+class FragmentMain : BaseFragment(R.layout.fragment_main), BackPressedListener {
+ lateinit var activity: MainActivity
+ private lateinit var productListView: RecyclerView
+ private lateinit var mAdapter: ShiftRecyclerAdapter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ // Inflate the layout for this fragment
+ setHasOptionsMenu(true)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ mAdapter = ShiftRecyclerAdapter(this) {
+ viewModel.deleteShift(it)
+ }
+ productListView = view.findViewById(R.id.list_item_view)
+ productListView.adapter = mAdapter
+
+ view.findViewById(R.id.fab1).setOnClickListener {
+ navigateToFragment(FragmentAddItem(), name = "additem")
+ }
+ }
+
+ override fun onSuccess(data: Any?) {
+ super.onSuccess(data)
+ if (data is List<*>) {
+ mAdapter.updateData(data as List)
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.delete_all -> {
+ deleteAllProducts()
+ return true
+ }
+
+ R.id.help -> {
+ AlertDialog.Builder(context)
+ .setTitle("Help & Support:")
+ .setView(R.layout.dialog_layout)
+ .setPositiveButton(android.R.string.yes) { arg0, arg1 -> arg0.dismiss() }
+ .create().show()
+ return true
+ }
+
+ R.id.filter_data -> {
+// val fragmentTransaction: FragmentTransaction =
+// activity.fragmentManager!!.beginTransaction()
+// fragmentTransaction.replace(R.id.container, FilterDataFragment())
+// .addToBackStack("filterdata").commit()
+ // Todo: filter shift
+
+ return true
+ }
+
+ R.id.sort_data -> {
+ sortData()
+ return true
+ }
+ R.id.clear_filter -> {
+ // Todo: Apply filter to list
+ return true
+ }
+
+ R.id.export_data -> {
+ if (checkStoragePermissions(activity)) {
+ AlertDialog.Builder(context)
+ .setTitle("Export?")
+ .setMessage("Exporting current filtered data. Continue?")
+ .setNegativeButton(android.R.string.no, null)
+ .setPositiveButton(android.R.string.yes) { arg0, arg1 -> ExportData() }
+ .create().show()
+ } else {
+ Toast.makeText(context, "Storage permissions required", Toast.LENGTH_SHORT)
+ .show()
+ }
+ return true
+ }
+
+ R.id.action_favorite -> {
+ AlertDialog.Builder(context)
+ .setTitle("Info:")
+ .setMessage(viewModel.getInformation())
+ .setPositiveButton(android.R.string.yes) { arg0, arg1 ->
+ arg0.dismiss()
+ }.create().show()
+ return true
+ }
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ private fun sortData() {
+ val groupName = Sortable.entries.map { it.label }.toTypedArray()
+ var sort = Sortable.ID
+
+ val sortAndOrder = viewModel.getSortAndOrder()
+ val checkedItem = Sortable.values().indexOf(sortAndOrder.first)
+
+ AlertDialog.Builder(context)
+ .setTitle("Sort by:")
+ .setSingleChoiceItems(
+ groupName,
+ checkedItem
+ ) { p0, p1 -> sort = Sortable.valueOf(groupName[p1]) }
+ .setPositiveButton("Ascending") { dialog, id ->
+ viewModel.setSortAndOrder(sort)
+ dialog.dismiss()
+ }.setNegativeButton("Descending") { dialog, id ->
+ viewModel.setSortAndOrder(sort, Order.DESCENDING)
+ dialog.dismiss()
+ }
+ .create().show()
+ }
+
+ private fun deleteAllProducts() {
+ requireContext().createDialog(
+ "Warning",
+ message = "Are you sure you want to delete all date?",
+ displayCancel = true,
+ okCallback = { _, _ ->
+ viewModel.deleteAllShifts()
+ }
+ )
+ }
+
+ private fun ExportData() {
+// val permission =
+// ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+// if (permission != PackageManager.PERMISSION_GRANTED) {
+// Toast.makeText(context, "Storage permissions not granted", Toast.LENGTH_SHORT).show()
+// return
+// }
+// shiftsDbhelper = ShiftsDbHelper(context)
+// val database = shiftsDbhelper!!.writableDatabase
+// val projection_export = arrayOf(
+// ShiftsEntry.COLUMN_SHIFT_DESCRIPTION,
+// ShiftsEntry.COLUMN_SHIFT_DATE,
+// ShiftsEntry.COLUMN_SHIFT_TIME_IN,
+// ShiftsEntry.COLUMN_SHIFT_TIME_OUT,
+// ShiftsEntry.COLUMN_SHIFT_BREAK,
+// ShiftsEntry.COLUMN_SHIFT_DURATION,
+// ShiftsEntry.COLUMN_SHIFT_TYPE,
+// ShiftsEntry.COLUMN_SHIFT_UNIT,
+// ShiftsEntry.COLUMN_SHIFT_PAYRATE,
+// ShiftsEntry.COLUMN_SHIFT_TOTALPAY
+// )
+// val cursor = activity.contentResolver.query(
+// ShiftsEntry.CONTENT_URI,
+// projection_export,
+// activity.selection,
+// activity.args,
+// activity.sortOrder
+// )
+// database.delete(ShiftsEntry.TABLE_NAME_EXPORT, null, null)
+// var totalDuration = 0.00f
+// var totalUnits = 0.00f
+// var totalPay = 0.00f
+// try {
+// while (cursor!!.moveToNext()) {
+// val descriptionColumnIndex =
+// cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION))
+// val dateColumnIndex =
+// cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DATE))
+// val timeInColumnIndex =
+// cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TIME_IN))
+// val timeOutColumnIndex =
+// cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TIME_OUT))
+// val durationColumnIndex =
+// cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DURATION))
+// val breakOutColumnIndex =
+// cursor.getInt(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_BREAK))
+// val typeColumnIndex =
+// cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TYPE))
+// val unitColumnIndex =
+// cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_UNIT))
+// val payrateColumnIndex =
+// cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_PAYRATE))
+// val totalpayColumnIndex =
+// cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TOTALPAY))
+// totalUnits = totalUnits + unitColumnIndex
+// totalDuration = totalDuration + durationColumnIndex
+// totalPay = totalPay + totalpayColumnIndex
+// val values = ContentValues()
+// values.put(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION, descriptionColumnIndex)
+// values.put(ShiftsEntry.COLUMN_SHIFT_DATE, dateColumnIndex)
+// values.put(ShiftsEntry.COLUMN_SHIFT_TIME_IN, timeInColumnIndex)
+// values.put(ShiftsEntry.COLUMN_SHIFT_TIME_OUT, timeOutColumnIndex)
+// values.put(ShiftsEntry.COLUMN_SHIFT_BREAK, breakOutColumnIndex)
+// values.put(ShiftsEntry.COLUMN_SHIFT_DURATION, durationColumnIndex)
+// values.put(ShiftsEntry.COLUMN_SHIFT_TYPE, typeColumnIndex)
+// values.put(ShiftsEntry.COLUMN_SHIFT_UNIT, unitColumnIndex)
+// values.put(ShiftsEntry.COLUMN_SHIFT_PAYRATE, payrateColumnIndex)
+// values.put(ShiftsEntry.COLUMN_SHIFT_TOTALPAY, totalpayColumnIndex)
+// database.insert(ShiftsEntry.TABLE_NAME_EXPORT, null, values)
+// }
+// } catch (e: Exception) {
+// Log.e("FragmentMain", "ExportData: ", e)
+// } finally {
+// val values = ContentValues()
+// values.put(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION, "")
+// values.put(ShiftsEntry.COLUMN_SHIFT_DATE, "")
+// values.put(ShiftsEntry.COLUMN_SHIFT_TIME_IN, "")
+// values.put(ShiftsEntry.COLUMN_SHIFT_TIME_OUT, "")
+// values.put(ShiftsEntry.COLUMN_SHIFT_BREAK, "Total duration:")
+// values.put(ShiftsEntry.COLUMN_SHIFT_DURATION, totalDuration)
+// values.put(ShiftsEntry.COLUMN_SHIFT_TYPE, "Total units:")
+// values.put(ShiftsEntry.COLUMN_SHIFT_UNIT, totalUnits)
+// values.put(ShiftsEntry.COLUMN_SHIFT_PAYRATE, "Total pay:")
+// values.put(ShiftsEntry.COLUMN_SHIFT_TOTALPAY, totalPay)
+// database.insert(ShiftsEntry.TABLE_NAME_EXPORT, null, values)
+// cursor!!.close()
+// }
+// val savePath = Environment.getExternalStorageDirectory().toString() + "/ShifttrackerTemp"
+// val file = File(savePath)
+// if (!file.exists()) {
+// file.mkdirs()
+// }
+// val sqLiteToExcel = SQLiteToExcel(context, "shifts.db", savePath)
+// sqLiteToExcel.exportSingleTable(
+// "shiftsexport",
+// "shifthistory.xls",
+// object : ExportListener {
+// override fun onStart() {}
+// override fun onCompleted(filePath: String) {
+// Toast.makeText(context, filePath, Toast.LENGTH_SHORT).show()
+// val newPath = Uri.parse("file://$savePath/shifthistory.xls")
+// val builder = VmPolicy.Builder()
+// StrictMode.setVmPolicy(builder.build())
+// val emailintent = Intent(Intent.ACTION_SEND)
+// emailintent.type = "application/vnd.ms-excel"
+// emailintent.putExtra(Intent.EXTRA_SUBJECT, "historic shifts")
+// emailintent.putExtra(Intent.EXTRA_TEXT, "I'm email body.")
+// emailintent.putExtra(Intent.EXTRA_STREAM, newPath)
+// startActivity(Intent.createChooser(emailintent, "Send Email"))
+// }
+//
+// override fun onError(e: Exception) {
+// println("Error msg: $e")
+// Toast.makeText(context, "Failed to Export data", Toast.LENGTH_SHORT).show()
+// }
+// })
+ }
+
+ @SuppressLint("DefaultLocale")
+ fun buildInfoString(
+ totalDuration: Float,
+ countOfTypeH: Int,
+ countOfTypeP: Int,
+ totalUnits: Float,
+ totalPay: Float,
+ lines: Int
+ ): String {
+ var textString: String
+ textString = "$lines Shifts"
+ if (countOfTypeH != 0 && countOfTypeP != 0) {
+ textString = "$textString ($countOfTypeH Hourly/$countOfTypeP Piece Rate)"
+ }
+ if (countOfTypeH != 0) {
+ textString = """
+ $textString
+ Total Hours: ${String.format("%.2f", totalDuration)}
+ """.trimIndent()
+ }
+ if (countOfTypeP != 0) {
+ textString = """
+ $textString
+ Total Units: ${String.format("%.2f", totalUnits)}
+ """.trimIndent()
+ }
+ if (totalPay != 0f) {
+ textString = """
+ $textString
+ Total Pay: ${"$"}${String.format("%.2f", totalPay)}
+ """.trimIndent()
+ }
+ return textString
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ println("request code$requestCode")
+ if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) {
+ if (grantResults.size > 0
+ && grantResults[0] == PackageManager.PERMISSION_GRANTED
+ ) {
+ exportDialog()
+ } else {
+ Toast.makeText(context, "Storage Permissions denied", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ fun exportDialog() {
+ AlertDialog.Builder(context)
+ .setTitle("Export?")
+ .setMessage("Exporting current filtered data. Continue?")
+ .setNegativeButton(android.R.string.no, null)
+ .setPositiveButton(android.R.string.yes) { arg0, arg1 -> ExportData() }.create().show()
+ }
+
+ fun checkStoragePermissions(activity: Activity?): Boolean {
+ var status = false
+ val permission = ActivityCompat.checkSelfPermission(
+ activity!!,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE
+ )
+ if (permission == PackageManager.PERMISSION_GRANTED) {
+ status = true
+ }
+ return status
+ }
+
+ companion object {
+ const val MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
+ }
+
+ override fun onBackPressed(): Boolean {
+ requireContext().createDialog(
+ title = "Leave?",
+ message = "Are you sure you want to exit Farmr?",
+ displayCancel = true,
+ okCallback = { _, _ ->
+ val intent = Intent(Intent.ACTION_MAIN)
+ intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
+ intent.addCategory(Intent.CATEGORY_HOME)
+ startActivity(intent)
+ requireActivity().finish()
+ exitProcess(0)
+ }
+ )
+ return true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/ui/FurtherInfoFragment.kt b/app/src/main/java/com/appttude/h_mal/farmr/ui/FurtherInfoFragment.kt
new file mode 100644
index 0000000..bda6dfd
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/ui/FurtherInfoFragment.kt
@@ -0,0 +1,111 @@
+package com.appttude.h_mal.farmr.ui
+
+import android.os.Bundle
+import android.view.View
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.ProgressBar
+import android.widget.TextView
+import com.appttude.h_mal.farmr.R
+import com.appttude.h_mal.farmr.base.BaseFragment
+import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
+import com.appttude.h_mal.farmr.model.ShiftType
+import com.appttude.h_mal.farmr.utils.CURRENCY
+import com.appttude.h_mal.farmr.utils.ID
+import com.appttude.h_mal.farmr.utils.hide
+import com.appttude.h_mal.farmr.utils.navigateToFragment
+import com.appttude.h_mal.farmr.utils.show
+import com.appttude.h_mal.farmr.viewmodel.MainViewModel
+
+class FurtherInfoFragment : BaseFragment(R.layout.fragment_futher_info) {
+ private lateinit var typeTV: TextView
+ private lateinit var descriptionTV: TextView
+ private lateinit var dateTV: TextView
+ private lateinit var times: TextView
+ private lateinit var breakTV: TextView
+ private lateinit var durationTV: TextView
+ private lateinit var unitsTV: TextView
+ private lateinit var payRateTV: TextView
+ private lateinit var totalPayTV: TextView
+ private lateinit var hourlyDetailHolder: LinearLayout
+ private lateinit var unitsHolder: LinearLayout
+ private lateinit var wholeView: LinearLayout
+ private lateinit var progressBarFI: ProgressBar
+ private lateinit var editButton: Button
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setTitle(getString(R.string.further_info_title))
+
+ progressBarFI = view.findViewById(R.id.progressBar_info)
+ wholeView = view.findViewById(R.id.further_info_view)
+ typeTV = view.findViewById(R.id.details_shift)
+ descriptionTV = view.findViewById(R.id.details_desc)
+ dateTV = view.findViewById(R.id.details_date)
+ times = view.findViewById(R.id.details_time)
+ breakTV = view.findViewById(R.id.details_breaks)
+ durationTV = view.findViewById(R.id.details_duration)
+ unitsTV = view.findViewById(R.id.details_units)
+ payRateTV = view.findViewById(R.id.details_pay_rate)
+ totalPayTV = view.findViewById(R.id.details_totalpay)
+ editButton = view.findViewById(R.id.details_edit)
+ hourlyDetailHolder = view.findViewById(R.id.details_hourly_details)
+ unitsHolder = view.findViewById(R.id.details_units_holder)
+
+ val id = arguments!!.getLong(ID)
+
+ editButton.setOnClickListener {
+ navigateToFragment(FragmentAddItem(), name = "additem", bundle = arguments!!)
+ }
+
+ setupView(id)
+ }
+
+ private fun setupView(id: Long) {
+ viewModel.getCurrentShift(id)?.run {
+ typeTV.text = type
+ descriptionTV.text = description
+ dateTV.text = date
+ payRateTV.text = rateOfPay.toString()
+ totalPayTV.text = StringBuilder(CURRENCY).append(totalPay).toString()
+
+ when (ShiftType.getEnumByType(type)) {
+ ShiftType.HOURLY -> {
+ hourlyDetailHolder.show()
+ unitsHolder.hide()
+ times.text = StringBuilder(timeIn).append("-").append(timeOut).toString()
+ breakTV.text = StringBuilder(breakMins).append("mins").toString()
+ durationTV.text = buildDurationSummary(this)
+ val paymentSummary =
+ StringBuilder().append(duration).append(" Hours @ ").append(CURRENCY)
+ .append(rateOfPay).append(" per Hour").append("\n")
+ .append("Equals: ").append(CURRENCY).append(totalPay)
+ totalPayTV.text = paymentSummary
+ }
+
+ ShiftType.PIECE -> {
+ hourlyDetailHolder.hide()
+ unitsHolder.show()
+ unitsTV.text = units.toString()
+
+ val paymentSummary =
+ StringBuilder().append(units).append(" Units @ ").append(CURRENCY)
+ .append(rateOfPay).append(" per Unit").append("\n")
+ .append("Equals: ").append(CURRENCY).append(totalPay)
+ totalPayTV.text = paymentSummary
+ }
+ }
+ }
+ }
+
+ private fun buildDurationSummary(shiftObject: ShiftObject): String {
+ val time = shiftObject.getHoursMinutesPairFromDuration()
+
+ val stringBuilder = StringBuilder().append(time.first).append(" Hours ").append(time.second)
+ .append(" Minutes ")
+ if (shiftObject.breakMins > 0) {
+ stringBuilder.append(" (+ ").append(shiftObject.breakMins).append(" minutes break)")
+ }
+ return stringBuilder.toString()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/ui/MainActivity.kt b/app/src/main/java/com/appttude/h_mal/farmr/ui/MainActivity.kt
new file mode 100644
index 0000000..87bb453
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/ui/MainActivity.kt
@@ -0,0 +1,94 @@
+package com.appttude.h_mal.farmr.ui
+
+import android.Manifest
+import android.R.string.cancel
+import android.R.string.ok
+import android.app.Activity
+import android.app.AlertDialog
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.view.Menu
+import androidx.appcompat.widget.Toolbar
+import androidx.core.app.ActivityCompat
+import com.appttude.h_mal.farmr.R
+import com.appttude.h_mal.farmr.base.BackPressedListener
+import com.appttude.h_mal.farmr.base.BaseActivity
+import com.appttude.h_mal.farmr.utils.createDialog
+import com.appttude.h_mal.farmr.utils.popBackStack
+import com.appttude.h_mal.farmr.viewmodel.MainViewModel
+import kotlin.system.exitProcess
+
+class MainActivity : BaseActivity() {
+ private lateinit var toolbar: Toolbar
+
+ var selection: String? = null
+ var args: Array? = null
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.main_view)
+ toolbar = findViewById(R.id.toolbar)
+ setSupportActionBar(toolbar)
+
+ verifyStoragePermissions(this)
+
+ val fragmentTransaction = supportFragmentManager.beginTransaction()
+ fragmentTransaction.replace(R.id.container, FragmentMain()).addToBackStack("main").commit()
+ }
+
+ 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 onBackPressed() {
+ val currentFragment = supportFragmentManager.findFragmentById(R.id.container)
+ if (currentFragment is BackPressedListener) {
+ currentFragment.onBackPressed()
+ } else {
+ if (supportFragmentManager.backStackEntryCount > 1) {
+ popBackStack()
+ } else {
+ super.onBackPressed()
+ }
+ }
+ }
+
+
+ fun setActionBarTitle(title: String?) {
+ toolbar.title = title
+ }
+
+ // Storage Permissions
+ private val REQUEST_EXTERNAL_STORAGE = 1
+ private val PERMISSIONS_STORAGE = arrayOf(
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE
+ )
+
+ /**
+ * Checks if the app has permission to write to device storage
+ *
+ * If the app does not has permission then the user will be prompted to grant permissions
+ *
+ * @param activity
+ */
+ fun verifyStoragePermissions(activity: Activity?) {
+ // Check if we have write permission
+ val permission = ActivityCompat.checkSelfPermission(
+ activity!!,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE
+ )
+ if (permission != PackageManager.PERMISSION_GRANTED) {
+ // We don't have permission so prompt the user
+ ActivityCompat.requestPermissions(
+ activity,
+ PERMISSIONS_STORAGE,
+ REQUEST_EXTERNAL_STORAGE
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/ui/ShiftRecyclerAdapter.kt b/app/src/main/java/com/appttude/h_mal/farmr/ui/ShiftRecyclerAdapter.kt
new file mode 100644
index 0000000..acb99da
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/ui/ShiftRecyclerAdapter.kt
@@ -0,0 +1,104 @@
+package com.appttude.h_mal.farmr.ui
+
+import android.app.AlertDialog
+import android.os.Bundle
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.RecyclerView
+import com.appttude.h_mal.farmr.R
+import com.appttude.h_mal.farmr.base.BaseRecyclerAdapter
+import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
+import com.appttude.h_mal.farmr.model.ShiftType
+import com.appttude.h_mal.farmr.utils.ID
+import com.appttude.h_mal.farmr.utils.navigateToFragment
+
+class ShiftRecyclerAdapter(
+ private val fragment: Fragment,
+ private val longPressCallback: (Long) -> Unit
+) : BaseRecyclerAdapter(
+ emptyViewId = R.layout.empty_list_view,
+ currentViewId = R.layout.list_item_1
+) {
+ override fun bindCurrentView(view: View, position: Int, data: ShiftObject) {
+ val descriptionTextView: TextView = view.findViewById(R.id.location)
+ val dateTextView: TextView = view.findViewById(R.id.date)
+ val totalPay: TextView = view.findViewById(R.id.total_pay)
+ val hoursView: TextView = view.findViewById(R.id.hours)
+ val h: TextView = view.findViewById(R.id.h)
+ val minutesView: TextView = view.findViewById(R.id.minutes)
+ val m: TextView = view.findViewById(R.id.m)
+ val editView: ImageView = view.findViewById(R.id.imageView)
+ h.text = "h"
+ m.text = "m"
+ val typeText: String = data.type
+ val descriptionText: String = data.description
+ val dateText: String = data.date
+ val totalPayText: String = data.totalPay.toString()
+
+ descriptionTextView.text = descriptionText
+ dateTextView.text = dateText
+ totalPay.text = totalPayText
+
+ when (ShiftType.getEnumByType(typeText)) {
+ ShiftType.HOURLY -> {
+ val time = data.getHoursMinutesPairFromDuration()
+
+ hoursView.text = time.first
+ minutesView.text = time.second
+ }
+
+ ShiftType.PIECE -> {
+ val unitsText: String = data.units.toString()
+
+ hoursView.text = unitsText
+ h.text = ""
+ minutesView.text = ""
+ m.text = "pcs"
+ }
+ }
+
+ val b: Bundle = Bundle()
+ b.putLong(ID, data.id)
+ view.setOnClickListener {
+ // Navigate to further info
+ fragment.navigateToFragment(
+ FurtherInfoFragment(),
+ bundle = b,
+ name = "furtherinfo"
+ )
+ }
+ editView.setOnClickListener {
+ // Navigate to edit
+ fragment.navigateToFragment(
+ FragmentAddItem(),
+ bundle = b,
+ name = "additem"
+ )
+ }
+ view.setOnLongClickListener {
+ AlertDialog.Builder(it.context)
+ .setMessage("Are you sure you want to delete")
+ .setPositiveButton("delete") { dialog, id -> longPressCallback.invoke(data.id) }
+ .setNegativeButton("cancel") { dialog, id ->
+ dialog?.dismiss()
+ }
+ .create().show()
+ true
+ }
+ }
+
+// override fun getItemId(position: Int): Long {
+// return if (list.isNullOrEmpty()) {
+// RecyclerView.NO_ID
+// } else {
+// list!![position].id
+// }
+//
+// }
+//
+// override fun setHasStableIds(hasStableIds: Boolean) {
+// super.setHasStableIds(true)
+// }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/SplashScreen.kt b/app/src/main/java/com/appttude/h_mal/farmr/ui/SplashScreen.kt
similarity index 94%
rename from app/src/main/java/com/appttude/h_mal/farmr/SplashScreen.kt
rename to app/src/main/java/com/appttude/h_mal/farmr/ui/SplashScreen.kt
index 878fb98..5310bdb 100644
--- a/app/src/main/java/com/appttude/h_mal/farmr/SplashScreen.kt
+++ b/app/src/main/java/com/appttude/h_mal/farmr/ui/SplashScreen.kt
@@ -1,4 +1,4 @@
-package com.appttude.h_mal.farmr
+package com.appttude.h_mal.farmr.ui
import android.app.Activity
import android.content.Intent
@@ -7,6 +7,7 @@ import android.os.Handler
import android.view.View
import android.widget.RelativeLayout
import androidx.core.app.ActivityOptionsCompat
+import com.appttude.h_mal.farmr.R
/**
* Created by h_mal on 27/06/2017.
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/utils/Constants.kt b/app/src/main/java/com/appttude/h_mal/farmr/utils/Constants.kt
new file mode 100644
index 0000000..59f48f8
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/utils/Constants.kt
@@ -0,0 +1,7 @@
+package com.appttude.h_mal.farmr.utils
+
+const val LEGACY = "LEGACY_"
+const val DATE_FORMAT = "yyyy-MM-dd"
+const val TIME_FORMAT = "hh:mm"
+const val ID = "ID"
+const val CURRENCY = "£"
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/utils/Formatting.kt b/app/src/main/java/com/appttude/h_mal/farmr/utils/Formatting.kt
new file mode 100644
index 0000000..1877d8d
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/utils/Formatting.kt
@@ -0,0 +1,81 @@
+package com.appttude.h_mal.farmr.utils
+
+import java.io.IOException
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Locale
+
+fun String.formatToTwoDp(): Float {
+ val formattedString = String.format("%.2f", this)
+ return formattedString.toFloat()
+}
+
+fun Float.formatToTwoDp(): Float {
+ val formattedString = String.format("%.2f", this)
+ return formattedString.toFloat()
+}
+
+fun Float.formatToTwoDpString(): String {
+ return formatToTwoDp().toString()
+}
+
+fun String.dateStringIsValid(): Boolean {
+ return DATE_FORMAT.toPattern().matcher(this).matches()
+}
+
+fun String.timeStringIsValid(): Boolean {
+ return TIME_FORMAT.toPattern().matcher(this).matches()
+}
+
+fun Calendar.getTimeString(): String {
+ val format = SimpleDateFormat(TIME_FORMAT, Locale.getDefault())
+ return format.format(time)
+}
+
+/**
+ * turns "HH:mm" into an hour and minutes pair
+ *
+ * eg:
+ * @param 13:45
+ * @return Pair(13, 45)
+ */
+fun convertTimeStringToHourMinutesPair(timeString: String): Pair {
+ val split = timeString.split(":")
+ if (split.size != 2) throw ArrayIndexOutOfBoundsException()
+ return Pair(split.first().toInt(), split[1].toInt())
+}
+
+
+/**
+ * calculate the duration between two 24 hour time strings minus the break in minutes
+ *
+ * can also calculate when time to string in past midnight eg: 23:00, 04:45, 30
+ * @return 5.75
+ */
+fun calculateDuration(timeIn: String, timeOut: String, breaks: Int): Float {
+ val timeFrom = convertTimeStringToHourMinutesPair(timeIn)
+ val timeTo = convertTimeStringToHourMinutesPair(timeOut)
+
+ val hoursIn = timeFrom.first
+ val minutesIn = timeFrom.second
+ val hoursOut = timeTo.first
+ val minutesOut = timeTo.second
+
+ var duration: Float = if (hoursOut > hoursIn) {
+ ((hoursOut.toFloat() + (minutesOut.toFloat() / 60)) - (hoursIn.toFloat() + (minutesIn.toFloat() / 60)))
+ } else {
+ (((hoursOut.toFloat() + (minutesOut.toFloat() / 60)) - (hoursIn.toFloat() + (minutesIn.toFloat() / 60))) + 24)
+ }
+ if ((breaks.toFloat() / 60) > duration) throw IOException("Breaks duration cannot be larger than shift duration")
+ duration -= (breaks.toFloat() / 60)
+
+ return duration.formatToTwoDp()
+}
+
+fun calculateDuration(timeIn: String?, timeOut: String?, breaks: Int?): Float {
+ val calendar by lazy { Calendar.getInstance() }
+ val insertTimeIn = timeIn ?: calendar.getTimeString()
+ val insertTimeOut = timeOut ?: calendar.getTimeString()
+
+ return calculateDuration(insertTimeIn, insertTimeOut, breaks ?: 0)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/utils/GenericsUtil.kt b/app/src/main/java/com/appttude/h_mal/farmr/utils/GenericsUtil.kt
new file mode 100644
index 0000000..cb0d32c
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/utils/GenericsUtil.kt
@@ -0,0 +1,38 @@
+package com.appttude.h_mal.farmr.utils
+
+import com.appttude.h_mal.farmr.model.Order
+import java.lang.reflect.ParameterizedType
+import kotlin.reflect.KClass
+
+@Suppress("UNCHECKED_CAST")
+fun Any.getGenericClassAt(position: Int): KClass =
+ ((javaClass.genericSuperclass as? ParameterizedType)
+ ?.actualTypeArguments?.getOrNull(position) as? Class)
+ ?.kotlin
+ ?: throw IllegalStateException("Can not find class from generic argument")
+
+/**
+ * @param validate when result is false then we trigger
+ * @param onError
+ *
+ *
+ * @sample
+ * var s: String?
+ * i.validate{!i.isNullOrEmpty()} { print("string is empty") }
+ */
+inline fun T.validateField(validate: (T) -> Boolean, onError: () -> Unit) {
+ if (!validate.invoke(this)) {
+ onError.invoke()
+ }
+}
+
+/**
+ * Returns a list of all elements sorted according to the specified comparator. In order of ascending or descending
+ * The sort is stable. It means that equal elements preserve their order relative to each other after sorting.
+ */
+inline fun > Iterable.sortedByOrder(order: Order = Order.ASCENDING, crossinline selector: (T) -> R?): List {
+ return when (order) {
+ Order.ASCENDING -> sortedWith(compareBy(selector))
+ Order.DESCENDING -> sortedWith(compareByDescending(selector))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/utils/ViewUtils.kt b/app/src/main/java/com/appttude/h_mal/farmr/utils/ViewUtils.kt
new file mode 100644
index 0000000..8432a0e
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/utils/ViewUtils.kt
@@ -0,0 +1,177 @@
+package com.appttude.h_mal.farmr.utils
+
+import android.app.Activity
+import android.app.AlertDialog
+import android.app.DatePickerDialog
+import android.app.TimePickerDialog
+import android.content.Context
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.Animation
+import android.view.animation.AnimationUtils
+import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
+import android.widget.Toast
+import androidx.annotation.AnimRes
+import androidx.annotation.IdRes
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentTransaction
+import com.appttude.h_mal.farmr.R
+import com.appttude.h_mal.farmr.ui.FragmentAddItem
+import java.util.Calendar
+
+fun View.show() {
+ this.visibility = View.VISIBLE
+}
+
+fun View.hide() {
+ this.visibility = View.GONE
+}
+
+fun Context.displayToast(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show()
+}
+
+fun Fragment.displayToast(message: String) {
+ Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
+}
+
+fun ViewGroup.generateView(layoutId: Int): View = LayoutInflater
+ .from(context)
+ .inflate(layoutId, this, false)
+
+fun Fragment.hideKeyboard() {
+ val imm = context?.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager?
+ imm?.hideSoftInputFromWindow(view?.windowToken, 0)
+}
+
+fun View.triggerAnimation(@AnimRes id: Int, complete: (View) -> Unit) {
+ val animation = AnimationUtils.loadAnimation(context, id)
+ animation.setAnimationListener(object : Animation.AnimationListener {
+ override fun onAnimationEnd(animation: Animation?) = complete(this@triggerAnimation)
+ override fun onAnimationStart(a: Animation?) {}
+ override fun onAnimationRepeat(a: Animation?) {}
+ })
+ startAnimation(animation)
+}
+
+fun Fragment.navigateToFragment(fragment: Fragment, @IdRes container: Int = R.id.container, name: String = "") {
+ val fragmentTransaction = requireActivity().supportFragmentManager.beginTransaction()
+ fragmentTransaction.replace(container, fragment).addToBackStack(name).commit()
+}
+
+fun Fragment.navigateToFragment(fragment: Fragment, @IdRes container: Int = R.id.container, name: String = "", bundle: Bundle) {
+ val fragmentTransaction = requireActivity().supportFragmentManager.beginTransaction()
+ fragmentTransaction.replace(container, fragment.apply { arguments = bundle }).addToBackStack(name).commit()
+}
+
+fun Context.createDialog(
+ title: String?,
+ message: String?,
+ displayCancel: Boolean = false,
+ displayOk: Boolean = true,
+ cancelCallback: DialogInterface.OnClickListener? = null,
+ okCallback: DialogInterface.OnClickListener? = null,
+) {
+ val builder = AlertDialog.Builder(this)
+ title?.let { builder.setTitle(it) }
+ message?.let { builder.setMessage(it) }
+ if (displayCancel) {
+ builder.setNegativeButton(android.R.string.cancel, cancelCallback)
+ }
+ if (displayOk) {
+ builder.setPositiveButton(android.R.string.ok, okCallback)
+ }
+
+ builder.create().show()
+}
+
+fun AppCompatActivity.popBackStack() {
+ supportFragmentManager.popBackStack()
+}
+
+fun EditText.setTimePicker(onSelected: (String) -> Unit) {
+ var mHoursOut: Int
+ var mMinutesOut: Int
+
+ setOnClickListener {
+ val mCurrentTime by lazy { Calendar.getInstance() }
+ if (!text.isNullOrEmpty()) {
+ // EditText contains text - lets try set the parse the text
+ try {
+ val convertedString = convertTimeStringToHourMinutesPair(text.toString())
+ mHoursOut = convertedString.first
+ mMinutesOut = convertedString.second
+ } catch (e: Exception) {
+ mHoursOut = mCurrentTime[Calendar.HOUR_OF_DAY]
+ mMinutesOut = mCurrentTime[Calendar.MINUTE]
+ }
+ } else {
+ mHoursOut = mCurrentTime[Calendar.HOUR_OF_DAY]
+ mMinutesOut = mCurrentTime[Calendar.MINUTE]
+ }
+ val mTimePicker = TimePickerDialog(this.context,
+ { _, selectedHour, selectedMinute ->
+ val ddTime = String.format("%02d", selectedHour) + ":" + String.format(
+ "%02d",
+ selectedMinute
+ )
+ setText(ddTime)
+ onSelected.invoke(ddTime)
+ }, mHoursOut, mMinutesOut, true
+ ) //Yes 24 hour time
+ mTimePicker.setTitle("Select Time")
+ mTimePicker.show()
+ }
+}
+
+fun EditText.setDatePicker(onSelected: (String) -> Unit) {
+ //To show current date in the datepicker
+ var mYear: Int
+ var mMonth: Int
+ var mDay: Int
+
+ val mCurrentDate by lazy { Calendar.getInstance() }
+
+ if (!text.isNullOrEmpty()) {
+ try {
+ val dateSplit = text.split("-")
+
+ mYear = dateSplit[0].toInt()
+ mMonth = dateSplit[1].toInt()
+ mMonth = if (mMonth == 1) {
+ 0
+ } else {
+ mMonth - 1
+ }
+ mDay = dateSplit[2].toInt()
+ } catch (e: Exception) {
+ mYear = mCurrentDate[Calendar.YEAR]
+ mMonth = mCurrentDate[Calendar.MONTH]
+ mDay = mCurrentDate[Calendar.DAY_OF_MONTH]
+ }
+ } else {
+ mYear = mCurrentDate[Calendar.YEAR]
+ mMonth = mCurrentDate[Calendar.MONTH]
+ mDay = mCurrentDate[Calendar.DAY_OF_MONTH]
+ }
+ val mDatePicker = DatePickerDialog(
+ (this.context),
+ { datepicker, selectedyear, selectedmonth, selectedday ->
+ var currentMonth = selectedmonth
+ val dateString = StringBuilder().append(selectedyear).append("-")
+ .append(String.format("%02d", (currentMonth + 1.also { currentMonth = it })))
+ .append("-")
+ .append(String.format("%02d", selectedday))
+ .toString()
+ setText(dateString)
+ onSelected.invoke(dateString)
+ }, mYear, mMonth, mDay
+ )
+ mDatePicker.setTitle("Select date")
+ mDatePicker.show()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/ApplicationViewModelFactory.kt b/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/ApplicationViewModelFactory.kt
new file mode 100644
index 0000000..16883c7
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/ApplicationViewModelFactory.kt
@@ -0,0 +1,22 @@
+package com.appttude.h_mal.farmr.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.appttude.h_mal.farmr.data.RepositoryImpl
+
+
+class ApplicationViewModelFactory(
+ private val repository: RepositoryImpl
+) : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ with(modelClass) {
+ return when {
+ isAssignableFrom(MainViewModel::class.java) -> MainViewModel(repository)
+ else -> throw IllegalArgumentException("Unknown ViewModel class")
+ } as T
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/MainViewModel.kt b/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/MainViewModel.kt
new file mode 100644
index 0000000..6d7281e
--- /dev/null
+++ b/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/MainViewModel.kt
@@ -0,0 +1,483 @@
+package com.appttude.h_mal.farmr.viewmodel
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import com.appttude.h_mal.farmr.base.BaseViewModel
+import com.appttude.h_mal.farmr.data.Repository
+import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
+import com.appttude.h_mal.farmr.data.prefs.DESCRIPTION
+import com.appttude.h_mal.farmr.data.prefs.TIME_IN
+import com.appttude.h_mal.farmr.data.prefs.TIME_OUT
+import com.appttude.h_mal.farmr.data.prefs.TYPE
+import com.appttude.h_mal.farmr.model.FilterStore
+import com.appttude.h_mal.farmr.model.Order
+import com.appttude.h_mal.farmr.model.Shift
+import com.appttude.h_mal.farmr.model.ShiftType
+import com.appttude.h_mal.farmr.model.Sortable
+import com.appttude.h_mal.farmr.utils.calculateDuration
+import com.appttude.h_mal.farmr.utils.dateStringIsValid
+import com.appttude.h_mal.farmr.utils.formatToTwoDp
+import com.appttude.h_mal.farmr.utils.getTimeString
+import com.appttude.h_mal.farmr.utils.sortedByOrder
+import com.appttude.h_mal.farmr.utils.timeStringIsValid
+import java.io.IOException
+import java.util.Calendar
+
+
+class MainViewModel(
+ private val repository: Repository
+) : BaseViewModel() {
+
+ private val _shiftLiveData = MutableLiveData>()
+ val shiftLiveData: LiveData> = _shiftLiveData
+
+ private var mSort: Sortable = Sortable.ID
+ private var mOrder: Order = Order.ASCENDING
+
+ private var mFilterStore: FilterStore? = null
+
+ private val observer = Observer> {
+ onSuccess(it.sortList(mSort, mOrder))
+ }
+
+ init {
+ // Load shifts into live data when view model has been instantiated
+ refreshLiveData()
+ shiftLiveData.observeForever(observer)
+ }
+
+ override fun onCleared() {
+ shiftLiveData.removeObserver(observer)
+ super.onCleared()
+ }
+
+ private fun List.sortList(sort: Sortable, order: Order): List {
+ return when (sort) {
+ Sortable.ID -> sortedByOrder(order) { it.id }
+ Sortable.TYPE -> sortedByOrder(order) { it.type }
+ Sortable.DATE -> sortedByOrder(order) { it.date }
+ Sortable.DESCRIPTION -> sortedByOrder(order) { it.description }
+ Sortable.DURATION -> sortedByOrder(order) { it.duration }
+ Sortable.UNITS -> sortedByOrder(order) { it.units }
+ Sortable.RATEOFPAY -> sortedByOrder(order) { it.rateOfPay }
+ Sortable.TOTALPAY -> sortedByOrder(order) { it.totalPay }
+ }
+ }
+
+ fun getSortAndOrder(): Pair {
+ return Pair(mSort, mOrder)
+ }
+
+ fun setSortAndOrder(sort: Sortable, order: Order = Order.ASCENDING) {
+ mSort = sort
+ mOrder = order
+ refreshLiveData()
+ }
+
+ fun getInformation(): String {
+ var totalDuration = 0.0f
+ var countOfTypeH = 0
+ var countOfTypeP = 0
+ var totalUnits = 0f
+ var totalPay = 0f
+ val lines = _shiftLiveData.value?.size ?: 0
+ _shiftLiveData.value?.forEach {
+ totalDuration += it.duration
+ when (ShiftType.getEnumByType(it.type)) {
+ ShiftType.HOURLY -> countOfTypeH += 1
+ ShiftType.PIECE -> countOfTypeP += 1
+ }
+ totalUnits += it.units
+ totalPay += it.totalPay
+ }
+
+ return buildInfoString(
+ totalDuration,
+ countOfTypeH,
+ countOfTypeP,
+ totalUnits,
+ totalPay,
+ lines
+ )
+ }
+
+ fun getCurrentShift(id: Long) = repository.readSingleShiftFromDatabase(id)
+
+ fun insertHourlyShift(
+ description: String,
+ date: String,
+ rateOfPay: Float,
+ timeIn: String?,
+ timeOut: String?,
+ breakMins: Int?,
+ ) {
+ // Validate inputs from the edit texts
+ (description.length > 3).validateField {
+ onError("Description length should be longer")
+ return
+ }
+ date.dateStringIsValid().validateField {
+ onError("Date format is invalid")
+ return
+ }
+ (rateOfPay.toFloat() >= 0.00).validateField {
+ onError("Rate of pay is invalid")
+ return
+ }
+ timeIn?.timeStringIsValid()?.validateField {
+ onError("Time in format is in correct")
+ return
+ }
+ timeOut?.timeStringIsValid()?.validateField {
+ onError("Time out format is in correct")
+ return
+ }
+ breakMins?.let { it > 0 }?.validateField {
+ onError("Break in minutes is invalid")
+ return
+ }
+
+ doTry {
+ insertShiftIntoDatabase(
+ ShiftType.HOURLY,
+ description,
+ date,
+ rateOfPay.formatToTwoDp(),
+ timeIn,
+ timeOut,
+ breakMins,
+ null
+ )
+ }
+
+ }
+
+ fun insertPieceRateShift(
+ description: String,
+ date: String,
+ units: Float,
+ rateOfPay: Float
+ ) {
+ // Validate inputs from the edit texts
+ (description.length < 3).validateField {
+ onError("Description length should be longer")
+ return
+ }
+ date.dateStringIsValid().validateField {
+ onError("Date format is invalid")
+ return
+ }
+ (rateOfPay >= 0.00).validateField {
+ onError("Rate of pay is invalid")
+ return
+ }
+ (units.toInt() >= 0).validateField {
+ onError("Units cannot be below zero")
+ return
+ }
+
+ doTry {
+ insertShiftIntoDatabase(
+ type = ShiftType.PIECE,
+ description = description,
+ date = date,
+ rateOfPay = rateOfPay.formatToTwoDp(),
+ null,
+ null,
+ null,
+ units = units
+ )
+ }
+ }
+
+ fun updateShift(
+ id: Long,
+ type: String? = null,
+ description: String? = null,
+ date: String? = null,
+ rateOfPay: String? = null,
+ timeIn: String? = null,
+ timeOut: String? = null,
+ breakMins: String? = null,
+ units: String? = null,
+ ) {
+ description?.let {
+ (it.length < 3).validateField {
+ onError("Description length should be longer")
+ return
+ }
+ }
+ date?.dateStringIsValid()?.validateField {
+ onError("Date format is invalid")
+ return
+ }
+ rateOfPay?.let {
+ (it.toFloat() >= 0.00).validateField {
+ onError("Rate of pay is invalid")
+ return
+ }
+ }
+ units?.let {
+ (it.toInt() >= 0).validateField {
+ onError("Units cannot be below zero")
+ return
+ }
+ }
+ timeIn?.timeStringIsValid()?.validateField {
+ onError("Time in format is in correct")
+ return
+ }
+ timeOut?.timeStringIsValid()?.validateField {
+ onError("Time out format is in correct")
+ return
+ }
+ breakMins?.let { it.toInt() > 0 }?.validateField {
+ onError("Break in minutes is invalid")
+ return
+ }
+
+ doTry {
+ updateShiftInDatabase(
+ id,
+ type = type?.let { ShiftType.getEnumByType(it) },
+ description = description,
+ date = date,
+ rateOfPay = rateOfPay?.toFloatOrNull(),
+ timeIn = timeIn,
+ timeOut = timeOut,
+ breakMins = breakMins?.toIntOrNull(),
+ units = units?.toFloatOrNull()
+ )
+ }
+ }
+
+ fun deleteShift(id: Long) {
+ if (!repository.deleteSingleShiftFromDatabase(id)) {
+ onError("Failed to delete shift")
+ } else {
+ refreshLiveData()
+ }
+ }
+
+ fun deleteAllShifts() {
+ if (!repository.deleteAllShiftsFromDatabase()) {
+ onError("Failed to delete all shifts from database")
+ } else {
+ refreshLiveData()
+ }
+ }
+
+ private fun updateShiftInDatabase(
+ id: Long,
+ type: ShiftType? = null,
+ description: String? = null,
+ date: String? = null,
+ rateOfPay: Float? = null,
+ timeIn: String? = null,
+ timeOut: String? = null,
+ breakMins: Int? = null,
+ units: Float? = null,
+ ) {
+ val currentShift = repository.readSingleShiftFromDatabase(id)?.copyToShift()
+ ?: throw IOException("Cannot update shift as it does not exist")
+
+ val shift = when (type) {
+ ShiftType.HOURLY -> {
+ // Shift type has changed so mandatory fields for hourly shift are now required as well
+ val insertTimeIn =
+ (timeIn ?: currentShift.timeIn) ?: throw IOException("No time in inserted")
+ val insertTimeOut =
+ (timeOut ?: currentShift.timeOut) ?: throw IOException("No time out inserted")
+ Shift(
+ description = description ?: currentShift.description,
+ date = date ?: currentShift.date,
+ timeIn = insertTimeIn,
+ timeOut = insertTimeOut,
+ breakMins = breakMins ?: currentShift.breakMins,
+ rateOfPay = rateOfPay ?: currentShift.rateOfPay
+ )
+ }
+
+ ShiftType.PIECE -> {
+ // Shift type has changed so mandatory fields for piece rate shift are now required as well
+ val insertUnits = (units ?: currentShift.units)
+ ?: throw IOException("Units must be inserted for piece rate shifts")
+ Shift(
+ description = description ?: currentShift.description,
+ date = date ?: currentShift.date,
+ units = insertUnits,
+ rateOfPay = rateOfPay ?: currentShift.rateOfPay
+ )
+ }
+
+ else -> {
+ if (timeIn == null && timeOut == null && units == null && breakMins == null && rateOfPay == null) {
+ // Updates to description or date field
+ currentShift.copy(
+ description = description ?: currentShift.description,
+ date = date ?: currentShift.date,
+ )
+ } else {
+ // Updating shifts where shift type has remained the same
+ when (currentShift.type) {
+ ShiftType.HOURLY -> {
+ val insertTimeIn = (timeIn ?: currentShift.timeIn) ?: throw IOException(
+ "No time in inserted"
+ )
+ val insertTimeOut = (timeOut ?: currentShift.timeOut)
+ ?: throw IOException("No time out inserted")
+ Shift(
+ description = description ?: currentShift.description,
+ date = date ?: currentShift.date,
+ timeIn = insertTimeIn,
+ timeOut = insertTimeOut,
+ breakMins = breakMins ?: currentShift.breakMins,
+ rateOfPay = rateOfPay ?: currentShift.rateOfPay
+ )
+ }
+
+ ShiftType.PIECE -> {
+ val insertUnits = (units ?: currentShift.units)
+ ?: throw IOException("Units must be inserted for piece rate shifts")
+ Shift(
+ description = description ?: currentShift.description,
+ date = date ?: currentShift.date,
+ units = insertUnits,
+ rateOfPay = rateOfPay ?: currentShift.rateOfPay
+ )
+ }
+ }
+ }
+ }
+ }
+
+ repository.updateShiftIntoDatabase(id, shift)
+ }
+
+ private fun insertShiftIntoDatabase(
+ type: ShiftType,
+ description: String,
+ date: String,
+ rateOfPay: Float,
+ timeIn: String?,
+ timeOut: String?,
+ breakMins: Int?,
+ units: Float?,
+ ) {
+ val shift = when (type) {
+ ShiftType.HOURLY -> {
+ if (timeIn.isNullOrBlank() && timeOut.isNullOrBlank()) throw IOException("Time in and time out are null")
+ val calendar by lazy { Calendar.getInstance() }
+ val insertTimeIn = timeIn ?: calendar.getTimeString()
+ val insertTimeOut = timeOut ?: calendar.getTimeString()
+
+ Shift(
+ description = description,
+ date = date,
+ timeIn = insertTimeIn,
+ timeOut = insertTimeOut,
+ breakMins = breakMins,
+ rateOfPay = rateOfPay
+ )
+ }
+
+ ShiftType.PIECE -> {
+ Shift(
+ description = description,
+ date = date,
+ units = units!!,
+ rateOfPay = rateOfPay,
+ )
+ }
+ }
+
+ repository.insertShiftIntoDatabase(shift)
+ }
+
+
+ private fun buildInfoString(
+ totalDuration: Float,
+ countOfHourly: Int,
+ countOfPiece: Int,
+ totalUnits: Float,
+ totalPay: Float,
+ lines: Int
+ ): String {
+ var textString: String
+ textString = "$lines Shifts"
+ if (countOfHourly != 0 && countOfPiece != 0) {
+ textString = "$textString ($countOfHourly Hourly/$countOfPiece Piece Rate)"
+ }
+ if (countOfHourly != 0) {
+ textString = """
+ $textString
+ Total Hours: ${String.format("%.2f", totalDuration)}
+ """.trimIndent()
+ }
+ if (countOfPiece != 0) {
+ textString = """
+ $textString
+ Total Units: ${String.format("%.2f", totalUnits)}
+ """.trimIndent()
+ }
+ if (totalPay != 0f) {
+ textString = """
+ $textString
+ Total Pay: ${"$"}${String.format("%.2f", totalPay)}
+ """.trimIndent()
+ }
+ return textString
+ }
+
+ private fun refreshLiveData() {
+ _shiftLiveData.postValue(repository.readShiftsFromDatabase())
+ }
+
+ private inline fun Boolean.validateField(failureCallback: () -> Unit) {
+ if (!this) failureCallback.invoke()
+ }
+
+ /**
+ * Lambda function that will invoke onError(...) on failure
+ * but update live data when successful
+ */
+ private inline fun doTry(operation: () -> Unit) {
+ try {
+ operation.invoke()
+ refreshLiveData()
+ } catch (e: Exception) {
+ onError(e)
+ }
+ }
+
+ fun setFiltrationDetails(
+ description: String?,
+ dateFrom: String?,
+ dateTo: String?,
+ type: String?
+ ) {
+ repository.setFilteringDetailsInPrefs(description, dateFrom, dateTo, type)
+ }
+
+ fun getFiltrationDetails(): FilterStore {
+ val prefs = repository.retrieveFilteringDetailsInPrefs()
+ mFilterStore = FilterStore(
+ prefs[DESCRIPTION],
+ prefs[TIME_IN],
+ prefs[TIME_OUT],
+ prefs[TYPE]
+ )
+ return mFilterStore!!
+ }
+
+ fun retrieveDurationText(mTimeIn: String?, mTimeOut: String?, mBreaks: Int): Float? {
+ try {
+ return calculateDuration(mTimeIn,mTimeOut,mBreaks)
+ }catch (e: IOException) {
+ onError(e)
+ }
+ return null
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/empty_list_view.xml b/app/src/main/res/layout/empty_list_view.xml
new file mode 100644
index 0000000..8112b34
--- /dev/null
+++ b/app/src/main/res/layout/empty_list_view.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_item.xml b/app/src/main/res/layout/fragment_add_item.xml
index 8f6152b..fd3bf6f 100644
--- a/app/src/main/res/layout/fragment_add_item.xml
+++ b/app/src/main/res/layout/fragment_add_item.xml
@@ -8,16 +8,9 @@
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
- tools:context="com.appttude.h_mal.farmr.FragmentAddItem"
+ tools:context="com.appttude.h_mal.farmr.ui.FragmentAddItem"
android:orientation="vertical">
-
-
diff --git a/app/src/main/res/layout/fragment_filter_data.xml b/app/src/main/res/layout/fragment_filter_data.xml
index 3b67fa9..3ebe08f 100644
--- a/app/src/main/res/layout/fragment_filter_data.xml
+++ b/app/src/main/res/layout/fragment_filter_data.xml
@@ -8,7 +8,7 @@
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
- tools:context="com.appttude.h_mal.farmr.FilterDataFragment">
+ tools:context="com.appttude.h_mal.farmr.ui.FilterDataFragment">
+ tools:context="com.appttude.h_mal.farmr.ui.FurtherInfoFragment">
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ tools:context="com.appttude.h_mal.farmr.ui.FragmentMain">
-
-
-
-
-
-
-
-
-
+
+ app:backgroundTint="@color/colorPrimary" />
diff --git a/app/src/main/res/layout/list_item_1.xml b/app/src/main/res/layout/list_item_1.xml
index 3ff54df..bdec9ea 100644
--- a/app/src/main/res/layout/list_item_1.xml
+++ b/app/src/main/res/layout/list_item_1.xml
@@ -126,7 +126,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
-
android:layout_alignParentTop="true"
app:srcCompat="@android:drawable/ic_menu_edit" />
diff --git a/app/src/main/res/layout/main_view.xml b/app/src/main/res/layout/main_view.xml
index 790957a..5dcff32 100644
--- a/app/src/main/res/layout/main_view.xml
+++ b/app/src/main/res/layout/main_view.xml
@@ -2,7 +2,7 @@
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
index 0135299..e4980fb 100644
--- a/app/src/main/res/menu/menu_main.xml
+++ b/app/src/main/res/menu/menu_main.xml
@@ -1,7 +1,7 @@