mirror of
https://github.com/hmalik144/Farmr.git
synced 2026-01-31 02:41:49 +00:00
- MVVM refactor
- Kodein DI added - Overhaul dirty code in views
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.appttude.h_mal.farmr.base
|
||||
|
||||
interface BackPressedListener {
|
||||
fun onBackPressed(): Boolean
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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?
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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))
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()) }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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?
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.appttude.h_mal.farmr.model
|
||||
|
||||
enum class Order(val label: String) {
|
||||
ASCENDING("Ascending"), DESCENDING("Descending")
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
11
app/src/main/java/com/appttude/h_mal/farmr/model/Sortable.kt
Normal file
11
app/src/main/java/com/appttude/h_mal/farmr/model/Sortable.kt
Normal 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")
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
229
app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentAddItem.kt
Normal file
229
app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentAddItem.kt
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
367
app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentMain.kt
Normal file
367
app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentMain.kt
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
// }
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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 = "£"
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
177
app/src/main/java/com/appttude/h_mal/farmr/utils/ViewUtils.kt
Normal file
177
app/src/main/java/com/appttude/h_mal/farmr/utils/ViewUtils.kt
Normal 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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
38
app/src/main/res/layout/empty_list_view.xml
Normal file
38
app/src/main/res/layout/empty_list_view.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user