Merge branch 'modern_architecture' into testsuite_expansion

# Conflicts:
#	app/build.gradle
This commit is contained in:
2023-08-28 11:00:03 +01:00
26 changed files with 302 additions and 403 deletions

2
.idea/kotlinc.xml generated
View File

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

View File

@@ -70,6 +70,6 @@ dependencies {
def kodein_version = "6.2.1" def kodein_version = "6.2.1"
implementation "org.kodein.di:kodein-di-generic-jvm:$kodein_version" implementation "org.kodein.di:kodein-di-generic-jvm:$kodein_version"
implementation "org.kodein.di:kodein-di-framework-android-x:$kodein_version" implementation "org.kodein.di:kodein-di-framework-android-x:$kodein_version"
/ * SQLite to excel */ / * jxl * /
implementation 'com.ajts.androidmads.SQLite2Excel:library:1.0.2' 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:name="com.appttude.h_mal.farmr.data.legacydb.ShiftProvider"
android:authorities="com.appttude.h_mal.farmr" android:authorities="com.appttude.h_mal.farmr"
android:exported="false" /> 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> </application>
</manifest> </manifest>

View File

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

View File

@@ -13,11 +13,6 @@ open class BaseRecyclerAdapter<T: Any>(
): RecyclerView.Adapter<ViewHolder>() { ): RecyclerView.Adapter<ViewHolder>() {
var list: List<T>? = null var list: List<T>? = null
fun updateData(newList: List<T>) {
list = newList
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if (list.isNullOrEmpty()) { return if (list.isNullOrEmpty()) {
val emptyViewHolder = parent.generateView(emptyViewId) val emptyViewHolder = parent.generateView(emptyViewId)

View File

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

View File

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

View File

@@ -7,5 +7,9 @@ enum class Sortable(val label: String) {
DESCRIPTION("Description"), DESCRIPTION("Description"),
DURATION("Added"), UNITS("Duration"), DURATION("Added"), UNITS("Duration"),
RATEOFPAY("Rate of pay"), 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.R
import com.appttude.h_mal.farmr.base.BaseFragment import com.appttude.h_mal.farmr.base.BaseFragment
import com.appttude.h_mal.farmr.model.ShiftType 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.utils.setDatePicker
import com.appttude.h_mal.farmr.viewmodel.MainViewModel 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setTitle(getString(R.string.title_activity_filter_data)) setTitle(getString(R.string.title_activity_filter_data))
LocationET = view.findViewById(R.id.filterLocationEditText) LocationET = view.findViewById(R.id.filterLocationEditText)
@@ -74,8 +74,8 @@ class FilterDataFragment : BaseFragment<MainViewModel>(R.layout.fragment_filter_
id: Long id: Long
) { ) {
type = when (position) { type = when (position) {
1 -> ShiftType.HOURLY.toString() 1 -> ShiftType.HOURLY.type
2 -> ShiftType.PIECE.toString() 2 -> ShiftType.PIECE.type
else -> return else -> return
} }
} }
@@ -89,4 +89,9 @@ class FilterDataFragment : BaseFragment<MainViewModel>(R.layout.fragment_filter_
override fun onClick(p0: View?) { override fun onClick(p0: View?) {
submitFiltrationDetails() 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.BackPressedListener
import com.appttude.h_mal.farmr.base.BaseFragment import com.appttude.h_mal.farmr.base.BaseFragment
import com.appttude.h_mal.farmr.model.ShiftType 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.ID
import com.appttude.h_mal.farmr.utils.createDialog 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.formatToTwoDpString
import com.appttude.h_mal.farmr.utils.hide import com.appttude.h_mal.farmr.utils.hide
import com.appttude.h_mal.farmr.utils.popBackStack 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 { viewModel.getCurrentShift(arguments!!.getLong(ID))?.run {
mLocationEditText.setText(description) mLocationEditText.setText(description)
mDateEditText.setText(date) mDateEditText.setText(date)
// Set types
mType = ShiftType.getEnumByType(type)
mDescription = description
mDate = date
mPayRate = rateOfPay
when (ShiftType.getEnumByType(type)) { when (ShiftType.getEnumByType(type)) {
ShiftType.HOURLY -> { ShiftType.HOURLY -> {
mHourlyRadioButton.isChecked = true mHourlyRadioButton.isChecked = true
@@ -132,16 +141,26 @@ class FragmentAddItem : BaseFragment<MainViewModel>(R.layout.fragment_add_item),
mBreakEditText.setText(breakMins.toString()) mBreakEditText.setText(breakMins.toString())
val durationText = "${duration.formatToTwoDpString()} Hours" val durationText = "${duration.formatToTwoDpString()} Hours"
mDurationTextView.text = durationText mDurationTextView.text = durationText
// Set fields
mTimeIn = timeIn
mTimeOut = timeOut
mBreaks = breakMins
} }
ShiftType.PIECE -> { ShiftType.PIECE -> {
mHourlyRadioButton.isChecked = false mHourlyRadioButton.isChecked = false
mPieceRadioButton.isChecked = true mPieceRadioButton.isChecked = true
mUnitEditText.setText(units.formatToTwoDpString()) mUnitEditText.setText(units.formatToTwoDpString())
// Set piece rate units
mUnits = units
} }
} }
mPayRateEditText.setText(rateOfPay.formatToTwoDpString()) mPayRateEditText.setText(rateOfPay.formatToTwoDpString())
mTotalPayTextView.text = totalPay.formatToTwoDpString() mTotalPayTextView.text = totalPay.formatToTwoDpString()
calculateTotalPay()
} }
// Return title // Return title
@@ -268,4 +287,11 @@ class FragmentAddItem : BaseFragment<MainViewModel>(R.layout.fragment_add_item),
return true 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.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.FileProvider
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
import com.appttude.h_mal.farmr.R import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.BackPressedListener import com.appttude.h_mal.farmr.base.BackPressedListener
import com.appttude.h_mal.farmr.base.BaseFragment import com.appttude.h_mal.farmr.base.BaseFragment
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
import com.appttude.h_mal.farmr.model.Order import com.appttude.h_mal.farmr.model.Order
import com.appttude.h_mal.farmr.model.Sortable 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.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.navigateToFragment
import com.appttude.h_mal.farmr.utils.show
import com.appttude.h_mal.farmr.viewmodel.MainViewModel import com.appttude.h_mal.farmr.viewmodel.MainViewModel
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import java.io.File
import kotlin.system.exitProcess import kotlin.system.exitProcess
class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPressedListener { class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPressedListener {
lateinit var activity: MainActivity
private lateinit var productListView: RecyclerView private lateinit var productListView: RecyclerView
private lateinit var emptyView: View
private lateinit var mAdapter: ShiftListAdapter private lateinit var mAdapter: ShiftListAdapter
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setTitle("Shift List")
// Inflate the layout for this fragment // Inflate the layout for this fragment
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
@@ -43,6 +53,15 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
} }
productListView = view.findViewById(R.id.list_item_view) productListView = view.findViewById(R.id.list_item_view)
productListView.adapter = mAdapter 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 { view.findViewById<FloatingActionButton>(R.id.fab1).setOnClickListener {
navigateToFragment(FragmentAddItem(), name = "additem") navigateToFragment(FragmentAddItem(), name = "additem")
@@ -58,8 +77,12 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
override fun onSuccess(data: Any?) { override fun onSuccess(data: Any?) {
super.onSuccess(data) super.onSuccess(data)
if (data is List<*>) { if (data is List<*>) {
@Suppress("UNCHECKED_CAST")
mAdapter.submitList(data as List<ShiftObject>) mAdapter.submitList(data as List<ShiftObject>)
} }
if (data is Success) {
displayToast(data.successMessage)
}
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@@ -79,12 +102,7 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
} }
R.id.filter_data -> { R.id.filter_data -> {
// val fragmentTransaction: FragmentTransaction = navigateToFragment(FilterDataFragment(), name = "filterdata")
// activity.fragmentManager!!.beginTransaction()
// fragmentTransaction.replace(R.id.container, FilterDataFragment())
// .addToBackStack("filterdata").commit()
// Todo: filter shift
return true return true
} }
@@ -92,8 +110,9 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
sortData() sortData()
return true return true
} }
R.id.clear_filter -> { R.id.clear_filter -> {
// Todo: Apply filter to list viewModel.setFiltrationDetails(null, null, null, null)
return true return true
} }
@@ -103,7 +122,7 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
.setTitle("Export?") .setTitle("Export?")
.setMessage("Exporting current filtered data. Continue?") .setMessage("Exporting current filtered data. Continue?")
.setNegativeButton(android.R.string.no, null) .setNegativeButton(android.R.string.no, null)
.setPositiveButton(android.R.string.yes) { arg0, arg1 -> ExportData() } .setPositiveButton(android.R.string.yes) { arg0, arg1 -> exportData() }
.create().show() .create().show()
} else { } else {
Toast.makeText(context, "Storage permissions required", Toast.LENGTH_SHORT) 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() { private fun exportData() {
// val permission = val permission =
// ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) ActivityCompat.checkSelfPermission(requireActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
// if (permission != PackageManager.PERMISSION_GRANTED) { if (permission != PackageManager.PERMISSION_GRANTED) {
// Toast.makeText(context, "Storage permissions not granted", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Storage permissions not granted", Toast.LENGTH_SHORT).show()
// return 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") val fileName = "shifthistory.xls"
fun buildInfoString( val file = File(requireContext().externalCacheDir, fileName)
totalDuration: Float,
countOfTypeH: Int, viewModel.createExcelSheet(file)?.let {
countOfTypeP: Int, val intent = Intent(Intent.ACTION_VIEW)
totalUnits: Float, val excelUri = FileProvider.getUriForFile(
totalPay: Float, requireContext(),
lines: Int requireContext().applicationContext.packageName + ".provider",
): String { file
var textString: String )
textString = "$lines Shifts" intent.setDataAndType(excelUri, "application/vnd.ms-excel")
if (countOfTypeH != 0 && countOfTypeP != 0) { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
textString = "$textString ($countOfTypeH Hourly/$countOfTypeP Piece Rate)" 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( override fun onRequestPermissionsResult(
@@ -335,7 +226,7 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
.setTitle("Export?") .setTitle("Export?")
.setMessage("Exporting current filtered data. Continue?") .setMessage("Exporting current filtered data. Continue?")
.setNegativeButton(android.R.string.no, null) .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 { fun checkStoragePermissions(activity: Activity?): Boolean {

View File

@@ -3,6 +3,7 @@ package com.appttude.h_mal.farmr.utils
import java.io.IOException import java.io.IOException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Date
import java.util.Locale import java.util.Locale
fun String.formatToTwoDp(): Float { fun String.formatToTwoDp(): Float {
@@ -32,6 +33,11 @@ fun Calendar.getTimeString(): String {
return format.format(time) 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 * turns "HH:mm" into an hour and minutes pair
* *

View File

@@ -1,11 +1,26 @@
package com.appttude.h_mal.farmr.viewmodel 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.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.appttude.h_mal.farmr.base.BaseViewModel import com.appttude.h_mal.farmr.base.BaseViewModel
import com.appttude.h_mal.farmr.data.Repository 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.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.DESCRIPTION
import com.appttude.h_mal.farmr.data.prefs.TIME_IN 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.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.Shift
import com.appttude.h_mal.farmr.model.ShiftType import com.appttude.h_mal.farmr.model.ShiftType
import com.appttude.h_mal.farmr.model.Sortable 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.calculateDuration
import com.appttude.h_mal.farmr.utils.convertDateString
import com.appttude.h_mal.farmr.utils.dateStringIsValid import com.appttude.h_mal.farmr.utils.dateStringIsValid
import com.appttude.h_mal.farmr.utils.formatToTwoDp import com.appttude.h_mal.farmr.utils.formatToTwoDp
import com.appttude.h_mal.farmr.utils.getTimeString import com.appttude.h_mal.farmr.utils.getTimeString
import com.appttude.h_mal.farmr.utils.sortedByOrder import com.appttude.h_mal.farmr.utils.sortedByOrder
import com.appttude.h_mal.farmr.utils.timeStringIsValid 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.io.IOException
import java.util.Calendar import java.util.Calendar
import java.util.Locale
class MainViewModel( class MainViewModel(
@@ -38,7 +63,8 @@ class MainViewModel(
private var mFilterStore: FilterStore? = null private var mFilterStore: FilterStore? = null
private val observer = Observer<List<ShiftObject>> { private val observer = Observer<List<ShiftObject>> {
onSuccess(it.sortList(mSort, mOrder)) val result = it.applyFilters().sortList(mSort, mOrder)
onSuccess(result)
} }
init { init {
@@ -47,6 +73,47 @@ class MainViewModel(
shiftLiveData.observeForever(observer) 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() { override fun onCleared() {
shiftLiveData.removeObserver(observer) shiftLiveData.removeObserver(observer)
super.onCleared() super.onCleared()
@@ -121,7 +188,7 @@ class MainViewModel(
onError("Date format is invalid") onError("Date format is invalid")
return return
} }
(rateOfPay.toFloat() >= 0.00).validateField { (rateOfPay >= 0.00).validateField {
onError("Rate of pay is invalid") onError("Rate of pay is invalid")
return return
} }
@@ -139,7 +206,7 @@ class MainViewModel(
} }
doTry { doTry {
insertShiftIntoDatabase( val result = insertShiftIntoDatabase(
ShiftType.HOURLY, ShiftType.HOURLY,
description, description,
date, date,
@@ -149,6 +216,8 @@ class MainViewModel(
breakMins, breakMins,
null null
) )
if (result) onSuccess(Success("Shift successfully added"))
} }
} }
@@ -178,7 +247,7 @@ class MainViewModel(
} }
doTry { doTry {
insertShiftIntoDatabase( val result = insertShiftIntoDatabase(
type = ShiftType.PIECE, type = ShiftType.PIECE,
description = description, description = description,
date = date, date = date,
@@ -188,6 +257,7 @@ class MainViewModel(
null, null,
units = units units = units
) )
if (result) onSuccess(Success("New shift successfully added"))
} }
} }
@@ -203,7 +273,7 @@ class MainViewModel(
units: Float? = null, units: Float? = null,
) { ) {
description?.let { description?.let {
(it.length < 3).validateField { (it.length > 3).validateField {
onError("Description length should be longer") onError("Description length should be longer")
return return
} }
@@ -213,7 +283,7 @@ class MainViewModel(
return return
} }
rateOfPay?.let { rateOfPay?.let {
(it.toFloat() >= 0.00).validateField { (it >= 0.00).validateField {
onError("Rate of pay is invalid") onError("Rate of pay is invalid")
return return
} }
@@ -232,13 +302,13 @@ class MainViewModel(
onError("Time out format is in correct") onError("Time out format is in correct")
return return
} }
breakMins?.let { it.toInt() > 0 }?.validateField { breakMins?.let { it >= 0 }?.validateField {
onError("Break in minutes is invalid") onError("Break in minutes is invalid")
return return
} }
doTry { doTry {
updateShiftInDatabase( val result = updateShiftInDatabase(
id, id,
type = type?.let { ShiftType.getEnumByType(it) }, type = type?.let { ShiftType.getEnumByType(it) },
description = description, description = description,
@@ -249,6 +319,8 @@ class MainViewModel(
breakMins = breakMins, breakMins = breakMins,
units = units units = units
) )
if (result) onSuccess(Success("Shift successfully updated"))
} }
} }
@@ -278,7 +350,7 @@ class MainViewModel(
timeOut: String? = null, timeOut: String? = null,
breakMins: Int? = null, breakMins: Int? = null,
units: Float? = null, units: Float? = null,
) { ): Boolean {
val currentShift = repository.readSingleShiftFromDatabase(id)?.copyToShift() val currentShift = repository.readSingleShiftFromDatabase(id)?.copyToShift()
?: throw IOException("Cannot update shift as it does not exist") ?: 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( private fun insertShiftIntoDatabase(
@@ -364,7 +436,7 @@ class MainViewModel(
timeOut: String?, timeOut: String?,
breakMins: Int?, breakMins: Int?,
units: Float?, units: Float?,
) { ): Boolean {
val shift = when (type) { val shift = when (type) {
ShiftType.HOURLY -> { ShiftType.HOURLY -> {
if (timeIn.isNullOrBlank() && timeOut.isNullOrBlank()) throw IOException("Time in and time out are null") 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, totalPay: Float,
lines: Int lines: Int
): String { ): String {
var textString: String val stringBuilder = StringBuilder("$lines Shifts").append("\n")
textString = "$lines Shifts"
if (countOfHourly != 0 && countOfPiece != 0) { if (countOfHourly != 0 && countOfPiece != 0) {
textString = "$textString ($countOfHourly Hourly/$countOfPiece Piece Rate)" stringBuilder.append(" ($countOfHourly Hourly/$countOfPiece Piece Rate)").append("\n")
} }
if (countOfHourly != 0) { if (countOfHourly != 0) {
textString = """ stringBuilder.append("Total Hours: ").append(totalDuration).append("\n")
$textString
Total Hours: ${String.format("%.2f", totalDuration)}
""".trimIndent()
} }
if (countOfPiece != 0) { if (countOfPiece != 0) {
textString = """ stringBuilder.append("Total Units: ").append(totalUnits).append("\n")
$textString
Total Units: ${String.format("%.2f", totalUnits)}
""".trimIndent()
} }
if (totalPay != 0f) { if (totalPay != 0f) {
textString = """ stringBuilder.append("Total Pay: ").append(CURRENCY).append(totalPay).append("\n")
$textString
Total Pay: ${"$"}${String.format("%.2f", totalPay)}
""".trimIndent()
} }
return textString return stringBuilder.toString()
} }
fun refreshLiveData() { fun refreshLiveData() {
@@ -458,6 +521,8 @@ class MainViewModel(
type: String? type: String?
) { ) {
repository.setFilteringDetailsInPrefs(description, dateFrom, dateTo, type) repository.setFilteringDetailsInPrefs(description, dateFrom, dateTo, type)
onSuccess(Success("Filter(s) successfully applied"))
refreshLiveData()
} }
fun getFiltrationDetails(): FilterStore { fun getFiltrationDetails(): FilterStore {
@@ -473,11 +538,90 @@ class MainViewModel(
fun retrieveDurationText(mTimeIn: String?, mTimeOut: String?, mBreaks: Int?): Float? { fun retrieveDurationText(mTimeIn: String?, mTimeOut: String?, mBreaks: Int?): Float? {
try { try {
return calculateDuration(mTimeIn,mTimeOut,mBreaks) return calculateDuration(mTimeIn, mTimeOut, mBreaks)
}catch (e: IOException) { } catch (e: IOException) {
onError(e) onError(e)
} }
return null 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" android:src="@drawable/add"
app:backgroundTint="@color/colorPrimary" /> app:backgroundTint="@color/colorPrimary" />
<include
android:visibility="gone"
layout="@layout/empty_list_view"
android:id="@+id/empty_view"/>
</RelativeLayout> </RelativeLayout>

View File

@@ -1,11 +1,9 @@
<resources> <resources>
<string name="app_name">Farmr</string> <string name="app_name">Farmr</string>
<string name="action_settings">Settings</string>
<string name="category_ach">Shifts</string> <string name="category_ach">Shifts</string>
<string name="add_item_title">Add Shift</string> <string name="add_item_title">Add Shift</string>
<string name="edit_item_title">Edit 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_failed">failed to insert Shift</string>
<string name="insert_item_successful">Shift successfully added</string> <string name="insert_item_successful">Shift successfully added</string>
<string name="update_item_failed">Update Failed</string> <string name="update_item_failed">Update Failed</string>
@@ -25,11 +23,6 @@
<!-- Example General settings --> <!-- Example General settings -->
<string name="pref_header_general">General</string> <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_title_display_name">Display name</string>
<string name="pref_default_display_name">John Smith</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 { buildscript {
ext { ext {
kotlin_version = '1.9.0' kotlin_version = '1.7.10'
} }
repositories { repositories {
google() google()