Merge pull request #14 from hmalik144/export_database

Export database
This commit is contained in:
2023-08-27 23:54:37 +01:00
committed by GitHub
26 changed files with 303 additions and 403 deletions

2
.idea/kotlinc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.0" />
<option name="version" value="1.7.10" />
</component>
</project>

View File

@@ -34,8 +34,6 @@ dependencies {
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'
@@ -48,4 +46,7 @@ dependencies {
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"
/ * jxl * /
implementation 'net.sourceforge.jexcelapi:jxl:2.6.12'
}

View File

@@ -37,6 +37,16 @@
android:name="com.appttude.h_mal.farmr.data.legacydb.ShiftProvider"
android:authorities="com.appttude.h_mal.farmr"
android:exported="false" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.appttude.h_mal.farmr.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_path" />
</provider>
</application>
</manifest>

View File

@@ -7,6 +7,7 @@ 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.utils.popBackStack
import com.appttude.h_mal.farmr.viewmodel.ApplicationViewModelFactory
import org.kodein.di.KodeinAware
import org.kodein.di.android.x.kodein
@@ -76,4 +77,6 @@ abstract class BaseFragment<V : BaseViewModel>(@LayoutRes contentLayoutId: Int)
fun setTitle(title: String) {
(requireActivity() as BaseActivity<*>).setTitleInActionBar(title)
}
fun popBackStack() = mActivity?.popBackStack()
}

View File

@@ -13,11 +13,6 @@ open class BaseRecyclerAdapter<T: Any>(
): 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)

View File

@@ -39,7 +39,7 @@ data class Shift(
timeOut,
duration,
breakTime,
duration,
0f,
rateOfPay,
(duration * rateOfPay).formatToTwoDp()
)

View File

@@ -4,10 +4,6 @@ enum class ShiftType(val type: String){
HOURLY("Hourly"),
PIECE("Piece Rate");
fun getEnumByType(type: String): ShiftType {
return values().first { it.type == type }
}
companion object {
fun getEnumByType(type: String): ShiftType {
return values().first { it.type == type }

View File

@@ -7,5 +7,9 @@ enum class Sortable(val label: String) {
DESCRIPTION("Description"),
DURATION("Added"), UNITS("Duration"),
RATEOFPAY("Rate of pay"),
TOTALPAY("Total Pay")
TOTALPAY("Total Pay");
companion object {
val entries = Sortable.values()
}
}

View File

@@ -0,0 +1,5 @@
package com.appttude.h_mal.farmr.model
data class Success(
val successMessage: String
)

View File

@@ -12,6 +12,7 @@ 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.model.Success
import com.appttude.h_mal.farmr.utils.setDatePicker
import com.appttude.h_mal.farmr.viewmodel.MainViewModel
@@ -32,7 +33,6 @@ class FilterDataFragment : BaseFragment<MainViewModel>(R.layout.fragment_filter_
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)
@@ -74,8 +74,8 @@ class FilterDataFragment : BaseFragment<MainViewModel>(R.layout.fragment_filter_
id: Long
) {
type = when (position) {
1 -> ShiftType.HOURLY.toString()
2 -> ShiftType.PIECE.toString()
1 -> ShiftType.HOURLY.type
2 -> ShiftType.PIECE.type
else -> return
}
}
@@ -89,4 +89,9 @@ class FilterDataFragment : BaseFragment<MainViewModel>(R.layout.fragment_filter_
override fun onClick(p0: View?) {
submitFiltrationDetails()
}
override fun onSuccess(data: Any?) {
super.onSuccess(data)
if (data is Success) popBackStack()
}
}

View File

@@ -14,8 +14,10 @@ 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.model.Success
import com.appttude.h_mal.farmr.utils.ID
import com.appttude.h_mal.farmr.utils.createDialog
import com.appttude.h_mal.farmr.utils.displayToast
import com.appttude.h_mal.farmr.utils.formatToTwoDpString
import com.appttude.h_mal.farmr.utils.hide
import com.appttude.h_mal.farmr.utils.popBackStack
@@ -123,6 +125,13 @@ class FragmentAddItem : BaseFragment<MainViewModel>(R.layout.fragment_add_item),
viewModel.getCurrentShift(arguments!!.getLong(ID))?.run {
mLocationEditText.setText(description)
mDateEditText.setText(date)
// Set types
mType = ShiftType.getEnumByType(type)
mDescription = description
mDate = date
mPayRate = rateOfPay
when (ShiftType.getEnumByType(type)) {
ShiftType.HOURLY -> {
mHourlyRadioButton.isChecked = true
@@ -132,16 +141,26 @@ class FragmentAddItem : BaseFragment<MainViewModel>(R.layout.fragment_add_item),
mBreakEditText.setText(breakMins.toString())
val durationText = "${duration.formatToTwoDpString()} Hours"
mDurationTextView.text = durationText
// Set fields
mTimeIn = timeIn
mTimeOut = timeOut
mBreaks = breakMins
}
ShiftType.PIECE -> {
mHourlyRadioButton.isChecked = false
mPieceRadioButton.isChecked = true
mUnitEditText.setText(units.formatToTwoDpString())
// Set piece rate units
mUnits = units
}
}
mPayRateEditText.setText(rateOfPay.formatToTwoDpString())
mTotalPayTextView.text = totalPay.formatToTwoDpString()
calculateTotalPay()
}
// Return title
@@ -268,4 +287,11 @@ class FragmentAddItem : BaseFragment<MainViewModel>(R.layout.fragment_add_item),
return true
}
override fun onSuccess(data: Any?) {
super.onSuccess(data)
if (data is Success) {
displayToast(data.successMessage)
popBackStack()
}
}
}

View File

@@ -6,31 +6,41 @@ import android.app.Activity
import android.app.AlertDialog
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.FileProvider
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
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.model.Success
import com.appttude.h_mal.farmr.utils.createDialog
import com.appttude.h_mal.farmr.utils.displayToast
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
import com.google.android.material.floatingactionbutton.FloatingActionButton
import java.io.File
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 emptyView: View
private lateinit var mAdapter: ShiftListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTitle("Shift List")
// Inflate the layout for this fragment
setHasOptionsMenu(true)
}
@@ -43,6 +53,15 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
}
productListView = view.findViewById(R.id.list_item_view)
productListView.adapter = mAdapter
emptyView = view.findViewById(R.id.empty_view)
mAdapter.registerAdapterDataObserver(object : AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
if (mAdapter.itemCount == 0) emptyView.show()
else emptyView.hide()
}
})
view.findViewById<FloatingActionButton>(R.id.fab1).setOnClickListener {
navigateToFragment(FragmentAddItem(), name = "additem")
@@ -58,8 +77,12 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
override fun onSuccess(data: Any?) {
super.onSuccess(data)
if (data is List<*>) {
@Suppress("UNCHECKED_CAST")
mAdapter.submitList(data as List<ShiftObject>)
}
if (data is Success) {
displayToast(data.successMessage)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@@ -79,12 +102,7 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
}
R.id.filter_data -> {
// val fragmentTransaction: FragmentTransaction =
// activity.fragmentManager!!.beginTransaction()
// fragmentTransaction.replace(R.id.container, FilterDataFragment())
// .addToBackStack("filterdata").commit()
// Todo: filter shift
navigateToFragment(FilterDataFragment(), name = "filterdata")
return true
}
@@ -92,8 +110,9 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
sortData()
return true
}
R.id.clear_filter -> {
// Todo: Apply filter to list
viewModel.setFiltrationDetails(null, null, null, null)
return true
}
@@ -103,7 +122,7 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
.setTitle("Export?")
.setMessage("Exporting current filtered data. Continue?")
.setNegativeButton(android.R.string.no, null)
.setPositiveButton(android.R.string.yes) { arg0, arg1 -> ExportData() }
.setPositiveButton(android.R.string.yes) { arg0, arg1 -> exportData() }
.create().show()
} else {
Toast.makeText(context, "Storage permissions required", Toast.LENGTH_SHORT)
@@ -159,157 +178,29 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
)
}
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 exportData() {
val permission =
ActivityCompat.checkSelfPermission(requireActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (permission != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(context, "Storage permissions not granted", Toast.LENGTH_SHORT).show()
return
}
@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)"
val fileName = "shifthistory.xls"
val file = File(requireContext().externalCacheDir, fileName)
viewModel.createExcelSheet(file)?.let {
val intent = Intent(Intent.ACTION_VIEW)
val excelUri = FileProvider.getUriForFile(
requireContext(),
requireContext().applicationContext.packageName + ".provider",
file
)
intent.setDataAndType(excelUri, "application/vnd.ms-excel")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent)
}
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(
@@ -335,7 +226,7 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
.setTitle("Export?")
.setMessage("Exporting current filtered data. Continue?")
.setNegativeButton(android.R.string.no, null)
.setPositiveButton(android.R.string.yes) { arg0, arg1 -> ExportData() }.create().show()
.setPositiveButton(android.R.string.yes) { arg0, arg1 -> exportData() }.create().show()
}
fun checkStoragePermissions(activity: Activity?): Boolean {

View File

@@ -3,6 +3,7 @@ package com.appttude.h_mal.farmr.utils
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
fun String.formatToTwoDp(): Float {
@@ -32,6 +33,11 @@ fun Calendar.getTimeString(): String {
return format.format(time)
}
fun String.convertDateString(format: String = DATE_FORMAT): Date? {
val formatter = SimpleDateFormat(format, Locale.getDefault())
return formatter.parse(this)
}
/**
* turns "HH:mm" into an hour and minutes pair
*

View File

@@ -1,11 +1,26 @@
package com.appttude.h_mal.farmr.viewmodel
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.os.Build
import android.os.Environment
import androidx.annotation.RequiresPermission
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.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._ID
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
@@ -15,14 +30,24 @@ 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.model.Success
import com.appttude.h_mal.farmr.utils.CURRENCY
import com.appttude.h_mal.farmr.utils.calculateDuration
import com.appttude.h_mal.farmr.utils.convertDateString
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 jxl.Workbook
import jxl.WorkbookSettings
import jxl.write.Label
import jxl.write.WritableWorkbook
import jxl.write.WriteException
import java.io.File
import java.io.IOException
import java.util.Calendar
import java.util.Locale
class MainViewModel(
@@ -38,7 +63,8 @@ class MainViewModel(
private var mFilterStore: FilterStore? = null
private val observer = Observer<List<ShiftObject>> {
onSuccess(it.sortList(mSort, mOrder))
val result = it.applyFilters().sortList(mSort, mOrder)
onSuccess(result)
}
init {
@@ -47,6 +73,47 @@ class MainViewModel(
shiftLiveData.observeForever(observer)
}
private fun List<ShiftObject>.applyFilters(): List<ShiftObject> {
val filter = getFiltrationDetails()
return filter { s ->
comparedStrings(filter.type, s.type) &&
comparedStringsContains(filter.description, s.description) &&
(isBetween(filter.dateFrom, filter.dateTo, s.date) ?: true)
}
}
private fun comparedStrings(first: String?, second: String?): Boolean {
return when (compareValues(first, second)) {
-1, 0, 1 -> true
else -> {
false
}
}
}
private fun comparedStringsContains(first: String?, second: String?): Boolean {
first?.let {
(second?.contains(it))?.let { c -> return c }
}
return comparedStrings(first, second)
}
private fun isBetween(fromDate: String?, toDate: String?, compareWith: String): Boolean? {
val first = fromDate?.convertDateString()
val second = toDate?.convertDateString()
if (first == null && second == null) return null
val compareDate = compareWith.convertDateString() ?: return null
if (second == null) return compareDate.after(first)
if (first == null) return compareDate.before(second)
return compareDate.after(first) && compareDate.before(second)
}
override fun onCleared() {
shiftLiveData.removeObserver(observer)
super.onCleared()
@@ -121,7 +188,7 @@ class MainViewModel(
onError("Date format is invalid")
return
}
(rateOfPay.toFloat() >= 0.00).validateField {
(rateOfPay >= 0.00).validateField {
onError("Rate of pay is invalid")
return
}
@@ -139,7 +206,7 @@ class MainViewModel(
}
doTry {
insertShiftIntoDatabase(
val result = insertShiftIntoDatabase(
ShiftType.HOURLY,
description,
date,
@@ -149,6 +216,8 @@ class MainViewModel(
breakMins,
null
)
if (result) onSuccess(Success("Shift successfully added"))
}
}
@@ -178,7 +247,7 @@ class MainViewModel(
}
doTry {
insertShiftIntoDatabase(
val result = insertShiftIntoDatabase(
type = ShiftType.PIECE,
description = description,
date = date,
@@ -188,6 +257,7 @@ class MainViewModel(
null,
units = units
)
if (result) onSuccess(Success("New shift successfully added"))
}
}
@@ -203,7 +273,7 @@ class MainViewModel(
units: Float? = null,
) {
description?.let {
(it.length < 3).validateField {
(it.length > 3).validateField {
onError("Description length should be longer")
return
}
@@ -213,7 +283,7 @@ class MainViewModel(
return
}
rateOfPay?.let {
(it.toFloat() >= 0.00).validateField {
(it >= 0.00).validateField {
onError("Rate of pay is invalid")
return
}
@@ -232,13 +302,13 @@ class MainViewModel(
onError("Time out format is in correct")
return
}
breakMins?.let { it.toInt() > 0 }?.validateField {
breakMins?.let { it >= 0 }?.validateField {
onError("Break in minutes is invalid")
return
}
doTry {
updateShiftInDatabase(
val result = updateShiftInDatabase(
id,
type = type?.let { ShiftType.getEnumByType(it) },
description = description,
@@ -249,6 +319,8 @@ class MainViewModel(
breakMins = breakMins,
units = units
)
if (result) onSuccess(Success("Shift successfully updated"))
}
}
@@ -278,7 +350,7 @@ class MainViewModel(
timeOut: String? = null,
breakMins: Int? = null,
units: Float? = null,
) {
): Boolean {
val currentShift = repository.readSingleShiftFromDatabase(id)?.copyToShift()
?: throw IOException("Cannot update shift as it does not exist")
@@ -352,7 +424,7 @@ class MainViewModel(
}
}
repository.updateShiftIntoDatabase(id, shift)
return repository.updateShiftIntoDatabase(id, shift)
}
private fun insertShiftIntoDatabase(
@@ -364,7 +436,7 @@ class MainViewModel(
timeOut: String?,
breakMins: Int?,
units: Float?,
) {
): Boolean {
val shift = when (type) {
ShiftType.HOURLY -> {
if (timeIn.isNullOrBlank() && timeOut.isNullOrBlank()) throw IOException("Time in and time out are null")
@@ -392,7 +464,7 @@ class MainViewModel(
}
}
repository.insertShiftIntoDatabase(shift)
return repository.insertShiftIntoDatabase(shift)
}
@@ -404,30 +476,21 @@ class MainViewModel(
totalPay: Float,
lines: Int
): String {
var textString: String
textString = "$lines Shifts"
val stringBuilder = StringBuilder("$lines Shifts").append("\n")
if (countOfHourly != 0 && countOfPiece != 0) {
textString = "$textString ($countOfHourly Hourly/$countOfPiece Piece Rate)"
stringBuilder.append(" ($countOfHourly Hourly/$countOfPiece Piece Rate)").append("\n")
}
if (countOfHourly != 0) {
textString = """
$textString
Total Hours: ${String.format("%.2f", totalDuration)}
""".trimIndent()
stringBuilder.append("Total Hours: ").append(totalDuration).append("\n")
}
if (countOfPiece != 0) {
textString = """
$textString
Total Units: ${String.format("%.2f", totalUnits)}
""".trimIndent()
stringBuilder.append("Total Units: ").append(totalUnits).append("\n")
}
if (totalPay != 0f) {
textString = """
$textString
Total Pay: ${"$"}${String.format("%.2f", totalPay)}
""".trimIndent()
stringBuilder.append("Total Pay: ").append(CURRENCY).append(totalPay).append("\n")
}
return textString
return stringBuilder.toString()
}
fun refreshLiveData() {
@@ -458,6 +521,8 @@ class MainViewModel(
type: String?
) {
repository.setFilteringDetailsInPrefs(description, dateFrom, dateTo, type)
onSuccess(Success("Filter(s) successfully applied"))
refreshLiveData()
}
fun getFiltrationDetails(): FilterStore {
@@ -473,11 +538,90 @@ class MainViewModel(
fun retrieveDurationText(mTimeIn: String?, mTimeOut: String?, mBreaks: Int?): Float? {
try {
return calculateDuration(mTimeIn,mTimeOut,mBreaks)
}catch (e: IOException) {
return calculateDuration(mTimeIn, mTimeOut, mBreaks)
} catch (e: IOException) {
onError(e)
}
return null
}
@RequiresPermission(WRITE_EXTERNAL_STORAGE)
fun createExcelSheet(file: File): File? {
val wbSettings = WorkbookSettings().apply {
locale = Locale("en", "EN")
}
try {
val workbook: WritableWorkbook = Workbook.createWorkbook(file, wbSettings)
val sheet = workbook.createSheet("Shifts", 0)
// Write column headers
val headers = listOf(
Label(0, 0, _ID),
Label(1, 0, COLUMN_SHIFT_TYPE),
Label(2, 0, COLUMN_SHIFT_DESCRIPTION),
Label(3, 0, COLUMN_SHIFT_DATE),
Label(4, 0, COLUMN_SHIFT_TIME_IN),
Label(5, 0, COLUMN_SHIFT_TIME_OUT),
Label(6, 0, "$COLUMN_SHIFT_BREAK (in mins)"),
Label(7, 0, COLUMN_SHIFT_DURATION),
Label(8, 0, COLUMN_SHIFT_UNIT),
Label(9, 0, COLUMN_SHIFT_PAYRATE),
Label(10, 0, COLUMN_SHIFT_TOTALPAY)
)
// table content
if (shiftLiveData.value.isNullOrEmpty()) {
onError("No data to parse into excel file")
return null
}
val sortAndOrder = getSortAndOrder()
val data = shiftLiveData.value!!.applyFilters().sortList(sortAndOrder.first, sortAndOrder.second)
var currentRow = 0
val cells = data.mapIndexed { index, shift ->
currentRow += 1
listOf(
Label(0, currentRow, shift.id.toString()),
Label(1, currentRow, shift.type),
Label(2, currentRow, shift.description),
Label(3, currentRow, shift.date),
Label(4, currentRow, shift.timeIn),
Label(5, currentRow, shift.timeOut),
Label(6, currentRow, shift.breakMins.toString()),
Label(7, currentRow, shift.duration.toString()),
Label(8, currentRow, shift.units.toString()),
Label(9, currentRow, shift.rateOfPay.toString()),
Label(10, currentRow, shift.totalPay.toString())
)
}.flatten()
currentRow += 1
val footer = listOf(
Label(0, currentRow, "Total:"),
Label(7, currentRow, data.sumOf { it.duration.toDouble() }.toString()),
Label(8, currentRow, data.sumOf { it.units.toDouble() }.toString()),
Label(10, currentRow, data.sumOf { it.totalPay.toDouble() }.toString())
)
val content = listOf(headers, cells, footer).flatten()
// Write content to sheet
try {
content.forEach { c -> sheet.addCell(c) }
} catch (e: WriteException) {
onError("Failed to write excel sheet")
return null
} catch (e: WriteException) {
onError("Failed to write excel sheet")
return null
}
workbook.write()
workbook.close()
return file
} catch (e: IOException) {
e.printStackTrace()
onError("Failed to generate excel sheet of shifts")
}
return null
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
</vector>

View File

@@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z" />
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-0.25 1.97,-0.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z" />
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recycler">
</androidx.recyclerview.widget.RecyclerView>
</FrameLayout>

View File

@@ -23,4 +23,9 @@
android:src="@drawable/add"
app:backgroundTint="@color/colorPrimary" />
<include
android:visibility="gone"
layout="@layout/empty_list_view"
android:id="@+id/empty_view"/>
</RelativeLayout>

View File

@@ -1,11 +1,9 @@
<resources>
<string name="app_name">Farmr</string>
<string name="action_settings">Settings</string>
<string name="category_ach">Shifts</string>
<string name="add_item_title">Add Shift</string>
<string name="edit_item_title">Edit Shift</string>
<string name="delete_item">Delete Shift</string>
<string name="insert_item_failed">failed to insert Shift</string>
<string name="insert_item_successful">Shift successfully added</string>
<string name="update_item_failed">Update Failed</string>
@@ -25,11 +23,6 @@
<!-- Example General settings -->
<string name="pref_header_general">General</string>
<string name="pref_title_social_recommendations">Enable social recommendations</string>
<string name="pref_description_social_recommendations">Recommendations for people to contact
based on your message history
</string>
<string name="pref_title_display_name">Display name</string>
<string name="pref_default_display_name">John Smith</string>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="."/>
</paths>

View File

@@ -2,7 +2,7 @@
buildscript {
ext {
kotlin_version = '1.9.0'
kotlin_version = '1.7.10'
}
repositories {
google()