- MVVM refactor

- Kodein DI added
 - Overhaul dirty code in views
This commit is contained in:
2023-08-25 17:44:54 +01:00
parent 815852ca98
commit cd20315b32
52 changed files with 2618 additions and 1591 deletions

View File

@@ -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"
}

View File

@@ -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))

View File

@@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".di.ShiftApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@@ -16,7 +17,7 @@
<!-- Splash screen -->
<activity
android:name="com.appttude.h_mal.farmr.SplashScreen"
android:name="com.appttude.h_mal.farmr.ui.SplashScreen"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Black.NoTitleBar"
tools:ignore="LockedOrientationActivity"
@@ -27,13 +28,13 @@
</intent-filter>
</activity>
<activity
android:name="com.appttude.h_mal.farmr.MainActivity"
android:parentActivityName="com.appttude.h_mal.farmr.SplashScreen"
android:name="com.appttude.h_mal.farmr.ui.MainActivity"
android:parentActivityName="com.appttude.h_mal.farmr.ui.SplashScreen"
android:theme="@style/AppTheme.NoActionBar"
android:exported="true"/>
<provider
android:name="com.appttude.h_mal.farmr.data.ShiftProvider"
android:name="com.appttude.h_mal.farmr.data.legacydb.ShiftProvider"
android:authorities="com.appttude.h_mal.farmr"
android:exported="false" />
</application>

View File

@@ -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<String> = arrayOf("", "Hourly", "Piece Rate")
private val listArgs: MutableList<String> = 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<View>(R.id.filterLocationEditText) as EditText?
dateFromET = rootView.findViewById<View>(R.id.fromdateInEditText) as EditText?
dateToET = rootView.findViewById<View>(R.id.filterDateOutEditText) as EditText?
typeSpinner = rootView.findViewById<View>(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<String> = 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<View>(R.id.submitFiltered) as Button
submit.setOnClickListener {
BuildQuery()
activity.args = listArgs.toTypedArray<String>()
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())
}
}
}

View File

@@ -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<Cursor?> {
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<View>(R.id.pd_ai) as ProgressBar
scrollView = rootView.findViewById<View>(R.id.total_view) as ScrollView
mRadioGroup = rootView.findViewById<View>(R.id.rg) as RadioGroup
mRadioButtonOne = rootView.findViewById<View>(R.id.hourly) as RadioButton
mRadioButtonTwo = rootView.findViewById<View>(R.id.piecerate) as RadioButton
mLocationEditText = rootView.findViewById<View>(R.id.locationEditText) as EditText
mDateEditText = rootView.findViewById<View>(R.id.dateEditText) as EditText
mTimeInEditText = rootView.findViewById<View>(R.id.timeInEditText) as EditText
mBreakEditText = rootView.findViewById<View>(R.id.breakEditText) as EditText
mTimeOutEditText = rootView.findViewById<View>(R.id.timeOutEditText) as EditText
mDurationTextView = rootView.findViewById<View>(R.id.ShiftDuration) as TextView
mUnitEditText = rootView.findViewById<View>(R.id.unitET) as EditText
mPayRateEditText = rootView.findViewById<View>(R.id.payrateET) as EditText
mTotalPayTextView = rootView.findViewById<View>(R.id.totalpayval) as TextView
hourlyDataView = rootView.findViewById<View>(R.id.hourly_data_holder) as LinearLayout
unitsHolder = rootView.findViewById<View>(R.id.units_holder) as LinearLayout
durationHolder = rootView.findViewById<View>(R.id.duration_holder) as LinearLayout
wholeView = rootView.findViewById<View>(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<View>(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<Cursor?> {
progressBarAI!!.visibility = View.VISIBLE
scrollView!!.visibility = View.GONE
val projection = arrayOf<String?>(
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<Cursor?>) {}
override fun onLoadFinished(loader: Loader<Cursor?>, 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
}
}

View File

@@ -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<Cursor> {
var mCursorAdapter: ShiftsCursorAdapter? = null
var shiftsDbhelper: ShiftsDbHelper? = null
lateinit var defaultLoaderCallback: LoaderManager.LoaderCallbacks<Cursor>
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<View>(R.id.list_item_view) as ListView
val emptyView = rootView.findViewById<View>(R.id.empty_view)
productListView.emptyView = emptyView
mCursorAdapter = ShiftsCursorAdapter(activity, null)
productListView.adapter = mCursorAdapter
loaderManager.initLoader(DEFAULT_LOADER, null, defaultLoaderCallback)
val fab = rootView.findViewById<FloatingActionButton>(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<String?>("")
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<String?>(
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<String?>(
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<Cursor> {
val projection = arrayOf<String?>(
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: Cursor) {
mCursorAdapter!!.swapCursor(cursor)
}
override fun onLoaderReset(loader: Loader<Cursor>) {
mCursorAdapter!!.swapCursor(null)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, 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
}
}
}

View File

@@ -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<Cursor> {
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<View>(R.id.progressBar_info) as ProgressBar?
wholeView = rootView.findViewById<View>(R.id.further_info_view) as LinearLayout?
typeTV = rootView.findViewById<View>(R.id.details_shift) as TextView?
descriptionTV = rootView.findViewById<View>(R.id.details_desc) as TextView?
dateTV = rootView.findViewById<View>(R.id.details_date) as TextView?
times = rootView.findViewById<View>(R.id.details_time) as TextView?
breakTV = rootView.findViewById<View>(R.id.details_breaks) as TextView?
durationTV = rootView.findViewById<View>(R.id.details_duration) as TextView?
unitsTV = rootView.findViewById<View>(R.id.details_units) as TextView?
payRateTV = rootView.findViewById<View>(R.id.details_pay_rate) as TextView?
totalPayTV = rootView.findViewById<View>(R.id.details_totalpay) as TextView?
editButton = rootView.findViewById<View>(R.id.details_edit) as Button?
hourlyDetailHolder = rootView.findViewById<View>(R.id.details_hourly_details) as LinearLayout?
unitsHolder = rootView.findViewById<View>(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<Cursor?> {
progressBarFI!!.visibility = View.VISIBLE
wholeView!!.visibility = View.GONE
val projection: Array<String?> = 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: 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<Cursor>) {}
companion object {
private val DEFAULT_LOADER: Int = 0
}
}

View File

@@ -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<String>? = 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<View>(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
)
}
}
}

View File

@@ -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<View>(R.id.location) as TextView
val dateTextView: TextView = view.findViewById<View>(R.id.date) as TextView
val totalPay: TextView = view.findViewById<View>(R.id.total_pay) as TextView
val hoursView: TextView = view.findViewById<View>(R.id.hours) as TextView
val h: TextView = view.findViewById<View>(R.id.h) as TextView
val minutesView: TextView = view.findViewById<View>(R.id.minutes) as TextView
val m: TextView = view.findViewById<View>(R.id.m) as TextView
val editView: ImageView = view.findViewById<View>(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<String> = 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<String> {
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)
}
}
}

View File

@@ -0,0 +1,5 @@
package com.appttude.h_mal.farmr.base
interface BackPressedListener {
fun onBackPressed(): Boolean
}

View File

@@ -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<V : BaseViewModel> : AppCompatActivity(), KodeinAware {
override val kodein by kodein()
private val factory by instance<ApplicationViewModelFactory>()
val viewModel: V by getViewModel()
private fun getViewModel(): Lazy<V> =
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 <A : AppCompatActivity> startActivity(activity: Class<A>) {
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)
}
}

View File

@@ -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<V : BaseViewModel>(@LayoutRes contentLayoutId: Int) :
Fragment(contentLayoutId), KodeinAware {
override val kodein by kodein()
private val factory by instance<ApplicationViewModelFactory>()
val viewModel: V by getActivityViewModel()
private fun getActivityViewModel() = createViewModelLazy<V>(
getGenericClassAt(0),
{ requireActivity().viewModelStore },
{ factory })
var mActivity: BaseActivity<*>? = null
private var shortAnimationDuration by Delegates.notNull<Int>()
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)
}
}

View File

@@ -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<T: Any>(
@LayoutRes private val emptyViewId: Int,
@LayoutRes private val currentViewId: Int
): RecyclerView.Adapter<ViewHolder>() {
var list: List<T>? = null
fun updateData(newList: List<T>) {
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)
}

View File

@@ -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<ViewState>()
val uiState: LiveData<ViewState> = _uiState
fun onStart() {
_uiState.postValue(ViewState.HasStarted)
}
fun <T : Any> onSuccess(result: T) {
_uiState.postValue(ViewState.HasData(result))
}
protected fun <E : Any> onError(error: E) {
_uiState.postValue(ViewState.HasError(error))
}
}

View File

@@ -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<ShiftObject>?
fun readSingleShiftFromDatabase(id: Long): ShiftObject?
fun deleteSingleShiftFromDatabase(id: Long): Boolean
fun deleteAllShiftsFromDatabase(): Boolean
fun retrieveSortAndOrderFromPref(): Pair<Sortable?, Order?>
fun setSortAndOrderFromPref(sortable: Sortable, order: Order)
fun retrieveFilteringDetailsInPrefs(): Map<String, String?>
fun setFilteringDetailsInPrefs(
description: String?,
timeIn: String?,
timeOut: String?,
type: String?
)
}

View File

@@ -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<ShiftObject>? {
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<Sortable?, Order?> {
return preferenceProvider.getSortableAndOrder()
}
override fun setSortAndOrderFromPref(sortable: Sortable, order: Order) {
preferenceProvider.saveSortableAndOrder(sortable, order)
}
override fun retrieveFilteringDetailsInPrefs(): Map<String, String?> {
return preferenceProvider.getFilteringDetails()
}
override fun setFilteringDetailsInPrefs(
description: String?,
timeIn: String?,
timeOut: String?,
type: String?
) {
preferenceProvider.saveFilteringDetails(description, timeIn, timeOut, type)
}
}

View File

@@ -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<String?>(
_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<ShiftObject>? {
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<String> = 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
)
}
}

View File

@@ -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<String, String> {
val hours: Int = floor(duration).toInt()
val minutes: Int = ((duration - hours) * 60).toInt()
return Pair(hours.toString(), minutes.toString())
}
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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<Sortable?, Order?> {
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<String, String?> {
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))
)
}
}

View File

@@ -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()) }
}
}

View File

@@ -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
)

View File

@@ -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?
)

View File

@@ -0,0 +1,5 @@
package com.appttude.h_mal.farmr.model
enum class Order(val label: String) {
ASCENDING("Ascending"), DESCENDING("Descending")
}

View File

@@ -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
)
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()
)
}
}

View File

@@ -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 }
}
}
}

View File

@@ -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")
}

View File

@@ -0,0 +1,7 @@
package com.appttude.h_mal.farmr.model
sealed class ViewState {
object HasStarted : ViewState()
class HasData<T : Any>(val data: T) : ViewState()
class HasError<T : Any>(val error: T) : ViewState()
}

View File

@@ -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<MainViewModel>(R.layout.fragment_filter_data),
AdapterView.OnItemSelectedListener, OnClickListener {
private val spinnerList: Array<String> =
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<String> =
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()
}
}

View File

@@ -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<MainViewModel>(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
}
}

View File

@@ -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<MainViewModel>(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<FloatingActionButton>(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<ShiftObject>)
}
}
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<String?>(
// 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<String>,
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
}
}

View File

@@ -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<MainViewModel>(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()
}
}

View File

@@ -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<MainViewModel>() {
private lateinit var toolbar: Toolbar
var selection: String? = null
var args: Array<String>? = 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
)
}
}
}

View File

@@ -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<ShiftObject>(
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)
// }
}

View File

@@ -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.

View File

@@ -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 = "£"

View File

@@ -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<Int, Int> {
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)
}

View File

@@ -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 <CLASS : Any> Any.getGenericClassAt(position: Int): KClass<CLASS> =
((javaClass.genericSuperclass as? ParameterizedType)
?.actualTypeArguments?.getOrNull(position) as? Class<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: Any?> 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 <T, R : Comparable<R>> Iterable<T>.sortedByOrder(order: Order = Order.ASCENDING, crossinline selector: (T) -> R?): List<T> {
return when (order) {
Order.ASCENDING -> sortedWith(compareBy(selector))
Order.DESCENDING -> sortedWith(compareByDescending(selector))
}
}

View File

@@ -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()
}

View File

@@ -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 <T : ViewModel> create(modelClass: Class<T>): T {
with(modelClass) {
return when {
isAssignableFrom(MainViewModel::class.java) -> MainViewModel(repository)
else -> throw IllegalArgumentException("Unknown ViewModel class")
} as T
}
}
}

View File

@@ -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<List<ShiftObject>>()
val shiftLiveData: LiveData<List<ShiftObject>> = _shiftLiveData
private var mSort: Sortable = Sortable.ID
private var mOrder: Order = Order.ASCENDING
private var mFilterStore: FilterStore? = null
private val observer = Observer<List<ShiftObject>> {
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<ShiftObject>.sortList(sort: Sortable, order: Order): List<ShiftObject> {
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<Sortable, Order> {
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
}
}

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<RelativeLayout
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:id="@+id/empty_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<TextView
android:id="@+id/empty_title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:fontFamily="sans-serif-medium"
android:paddingTop="16dp"
android:text="Shift list empty"
android:textAppearance="?android:textAppearanceMedium"/>
<TextView
android:id="@+id/empty_subtitle_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/empty_title_text"
android:layout_centerHorizontal="true"
android:fontFamily="sans-serif"
android:paddingTop="8dp"
android:text="add shift to begin"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="#A2AAB0"/>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -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">
<ProgressBar
android:id="@+id/pd_ai"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -189,7 +182,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Break"
android:text="@string/break_res"
android:textAppearance="@style/TextAppearance.AppCompat" />
<EditText
@@ -198,7 +191,7 @@
android:layout_height="wrap_content"
android:layout_weight="2"
android:ems="10"
android:hint="Break in minutes"
android:hint="@string/insert_break_in_minutes"
android:inputType="number"
android:selectAllOnFocus="true" />
</LinearLayout>

View File

@@ -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">
<LinearLayout
android:layout_width="match_parent"

View File

@@ -7,7 +7,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.FurtherInfoFragment">
tools:context="com.appttude.h_mal.farmr.ui.FurtherInfoFragment">
<ProgressBar
android:visibility="gone"

View File

@@ -2,44 +2,16 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:ads="http://schemas.android.com/apk/res-auto"
tools:context="com.appttude.h_mal.farmr.FragmentMain">
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.appttude.h_mal.farmr.ui.FragmentMain">
<ListView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_item_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/list_item_1">
</ListView>
<RelativeLayout
android:id="@+id/empty_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<TextView
android:id="@+id/empty_title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:fontFamily="sans-serif-medium"
android:paddingTop="16dp"
android:text="Shift list empty"
android:textAppearance="?android:textAppearanceMedium"/>
<TextView
android:id="@+id/empty_subtitle_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/empty_title_text"
android:layout_centerHorizontal="true"
android:fontFamily="sans-serif"
android:paddingTop="8dp"
android:text="add shift to begin"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="#A2AAB0"/>
</RelativeLayout>
</androidx.recyclerview.widget.RecyclerView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab1"
@@ -49,6 +21,6 @@
android:layout_alignParentBottom="true"
android:layout_margin="@dimen/fab_margin"
android:src="@drawable/add"
ads:backgroundTint="@color/colorPrimary" />
app:backgroundTint="@color/colorPrimary" />
</RelativeLayout>

View File

@@ -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" />

View File

@@ -2,7 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity"
tools:context=".ui.MainActivity"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

View File

@@ -1,7 +1,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.appttude.h_mal.farmr.MainActivity">
tools:context="com.appttude.h_mal.farmr.ui.MainActivity">
<item
android:id="@+id/action_favorite"
android:icon="@drawable/image_i_64"

View File

@@ -102,4 +102,6 @@
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="further_info_title">Shift Details</string>
<string name="insert_break_in_minutes">insert break in minutes</string>
<string name="break_res">Break</string>
</resources>