From cd20315b327897edfbbadb1f825dd6b9dee3cd6d Mon Sep 17 00:00:00 2001 From: "h.malik144@gmail.com" Date: Fri, 25 Aug 2023 17:44:54 +0100 Subject: [PATCH] - MVVM refactor - Kodein DI added - Overhaul dirty code in views --- app/build.gradle | 15 + .../h_mal/farmr/data/ShiftProviderTest.kt | 29 +- app/src/main/AndroidManifest.xml | 9 +- .../h_mal/farmr/FilterDataFragment.kt | 144 ----- .../appttude/h_mal/farmr/FragmentAddItem.kt | 507 ------------------ .../com/appttude/h_mal/farmr/FragmentMain.kt | 459 ---------------- .../h_mal/farmr/FurtherInfoFragment.kt | 161 ------ .../com/appttude/h_mal/farmr/MainActivity.kt | 116 ---- .../h_mal/farmr/ShiftsCursorAdapter.kt | 117 ---- .../h_mal/farmr/base/BackPressedListener.kt | 5 + .../appttude/h_mal/farmr/base/BaseActivity.kt | 58 ++ .../appttude/h_mal/farmr/base/BaseFragment.kt | 79 +++ .../h_mal/farmr/base/BaseRecyclerAdapter.kt | 47 ++ .../h_mal/farmr/base/BaseViewModel.kt | 26 + .../appttude/h_mal/farmr/data/Repository.kt | 24 + .../h_mal/farmr/data/RepositoryImpl.kt | 71 +++ .../farmr/data/legacydb/LegacyDatabase.kt | 167 ++++++ .../h_mal/farmr/data/legacydb/ShiftObject.kt | 28 + .../data/{ => legacydb}/ShiftProvider.kt | 6 +- .../data/{ => legacydb}/ShiftsContract.kt | 2 +- .../data/{ => legacydb}/ShiftsDbHelper.kt | 4 +- .../farmr/data/prefs/PreferencesProvider.kt | 65 +++ .../h_mal/farmr/di/ShiftApplication.kt | 28 + .../h_mal/farmr/model/DatabaseShift.kt | 11 + .../appttude/h_mal/farmr/model/FilterStore.kt | 8 + .../com/appttude/h_mal/farmr/model/Order.kt | 5 + .../com/appttude/h_mal/farmr/model/Shift.kt | 76 ++- .../appttude/h_mal/farmr/model/ShiftType.kt | 14 +- .../appttude/h_mal/farmr/model/Sortable.kt | 11 + .../appttude/h_mal/farmr/model/ViewState.kt | 7 + .../h_mal/farmr/ui/FilterDataFragment.kt | 92 ++++ .../h_mal/farmr/ui/FragmentAddItem.kt | 229 ++++++++ .../appttude/h_mal/farmr/ui/FragmentMain.kt | 367 +++++++++++++ .../h_mal/farmr/ui/FurtherInfoFragment.kt | 111 ++++ .../appttude/h_mal/farmr/ui/MainActivity.kt | 94 ++++ .../h_mal/farmr/ui/ShiftRecyclerAdapter.kt | 104 ++++ .../h_mal/farmr/{ => ui}/SplashScreen.kt | 3 +- .../appttude/h_mal/farmr/utils/Constants.kt | 7 + .../appttude/h_mal/farmr/utils/Formatting.kt | 81 +++ .../h_mal/farmr/utils/GenericsUtil.kt | 38 ++ .../appttude/h_mal/farmr/utils/ViewUtils.kt | 177 ++++++ .../viewmodel/ApplicationViewModelFactory.kt | 22 + .../h_mal/farmr/viewmodel/MainViewModel.kt | 483 +++++++++++++++++ app/src/main/res/layout/empty_list_view.xml | 38 ++ app/src/main/res/layout/fragment_add_item.xml | 13 +- .../main/res/layout/fragment_filter_data.xml | 2 +- .../main/res/layout/fragment_futher_info.xml | 2 +- app/src/main/res/layout/fragment_main.xml | 40 +- app/src/main/res/layout/list_item_1.xml | 1 - app/src/main/res/layout/main_view.xml | 2 +- app/src/main/res/menu/menu_main.xml | 2 +- app/src/main/res/values/strings.xml | 2 + 52 files changed, 2618 insertions(+), 1591 deletions(-) delete mode 100644 app/src/main/java/com/appttude/h_mal/farmr/FilterDataFragment.kt delete mode 100644 app/src/main/java/com/appttude/h_mal/farmr/FragmentAddItem.kt delete mode 100644 app/src/main/java/com/appttude/h_mal/farmr/FragmentMain.kt delete mode 100644 app/src/main/java/com/appttude/h_mal/farmr/FurtherInfoFragment.kt delete mode 100644 app/src/main/java/com/appttude/h_mal/farmr/MainActivity.kt delete mode 100644 app/src/main/java/com/appttude/h_mal/farmr/ShiftsCursorAdapter.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/base/BackPressedListener.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/base/BaseActivity.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/base/BaseFragment.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/base/BaseRecyclerAdapter.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/base/BaseViewModel.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/data/Repository.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/data/RepositoryImpl.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/LegacyDatabase.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftObject.kt rename app/src/main/java/com/appttude/h_mal/farmr/data/{ => legacydb}/ShiftProvider.kt (97%) rename app/src/main/java/com/appttude/h_mal/farmr/data/{ => legacydb}/ShiftsContract.kt (96%) rename app/src/main/java/com/appttude/h_mal/farmr/data/{ => legacydb}/ShiftsDbHelper.kt (95%) create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/data/prefs/PreferencesProvider.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/di/ShiftApplication.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/model/DatabaseShift.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/model/FilterStore.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/model/Order.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/model/Sortable.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/model/ViewState.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/ui/FilterDataFragment.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentAddItem.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentMain.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/ui/FurtherInfoFragment.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/ui/MainActivity.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/ui/ShiftRecyclerAdapter.kt rename app/src/main/java/com/appttude/h_mal/farmr/{ => ui}/SplashScreen.kt (94%) create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/utils/Constants.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/utils/Formatting.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/utils/GenericsUtil.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/utils/ViewUtils.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/viewmodel/ApplicationViewModelFactory.kt create mode 100644 app/src/main/java/com/appttude/h_mal/farmr/viewmodel/MainViewModel.kt create mode 100644 app/src/main/res/layout/empty_list_view.xml 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 @@ + tools:context="com.appttude.h_mal.farmr.ui.MainActivity"> Hello blank fragment Shift Details + insert break in minutes + Break