- Shift list exportation completed

This commit is contained in:
2023-08-27 23:17:52 +01:00
parent 22982f1482
commit c6316ca910
12 changed files with 269 additions and 198 deletions

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

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

@@ -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)
@@ -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,11 +6,13 @@ 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 com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.BackPressedListener
@@ -22,15 +24,17 @@ 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 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 mAdapter: ShiftListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTitle("Shift List")
// Inflate the layout for this fragment
setHasOptionsMenu(true)
}
@@ -58,6 +62,7 @@ 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>)
}
}
@@ -79,12 +84,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 +92,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 +104,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 +160,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 +208,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,11 +521,12 @@ class MainViewModel(
type: String?
) {
repository.setFilteringDetailsInPrefs(description, dateFrom, dateTo, type)
onSuccess(Success("Filter(s) successfully applied"))
}
fun getFiltrationDetails(): FilterStore {
val prefs = repository.retrieveFilteringDetailsInPrefs()
mFilterStore = FilterStore(
mFilterStore = FilterStore(
prefs[DESCRIPTION],
prefs[TIME_IN],
prefs[TIME_OUT],
@@ -473,11 +537,89 @@ 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
val data = shiftLiveData.value
if (data.isNullOrEmpty()) {
onError("No data to parse into excel file")
return null
}
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
}
}

View File

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