Merge pull request #43 from hmalik144/calender_page_feature

Calender page feature
This commit is contained in:
2023-09-15 13:39:51 +01:00
committed by GitHub
46 changed files with 1452 additions and 205 deletions

View File

@@ -14,8 +14,8 @@ android {
applicationId "com.appttude.h_mal.farmr" applicationId "com.appttude.h_mal.farmr"
minSdkVersion MIN_SDK_VERSION minSdkVersion MIN_SDK_VERSION
targetSdkVersion TARGET_SDK_VERSION targetSdkVersion TARGET_SDK_VERSION
versionCode 6 versionCode 7
versionName "2.4" versionName "3.0"
testInstrumentationRunner 'com.appttude.h_mal.farmr.application.TestRunner' testInstrumentationRunner 'com.appttude.h_mal.farmr.application.TestRunner'
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
@@ -88,4 +88,6 @@ dependencies {
implementation "org.kodein.di:kodein-di-framework-android-core:$KODEIN_VERSION" implementation "org.kodein.di:kodein-di-framework-android-core:$KODEIN_VERSION"
/ * jxl * / / * jxl * /
implementation "net.sourceforge.jexcelapi:jxl:$JEXCEL_VERSION" implementation "net.sourceforge.jexcelapi:jxl:$JEXCEL_VERSION"
/ * calendar view * /
implementation 'com.applandeo:material-calendar-view:1.7.0'
} }

View File

@@ -15,6 +15,8 @@ import androidx.test.espresso.matcher.RootMatchers.withDecorView
import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import com.appttude.h_mal.farmr.application.TestAppClass import com.appttude.h_mal.farmr.application.TestAppClass
import com.appttude.h_mal.farmr.ui.utils.EspressoHelper.waitFor
import com.appttude.h_mal.farmr.ui.utils.generateShifts
import com.appttude.h_mal.farmr.ui.utils.getShifts import com.appttude.h_mal.farmr.ui.utils.getShifts
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.hamcrest.Matcher import org.hamcrest.Matcher
@@ -73,16 +75,6 @@ open class BaseTest<A : Activity>(
open fun afterLaunch() {} open fun afterLaunch() {}
open fun testFinished() {} open fun testFinished() {}
fun waitFor(delay: Long) {
Espresso.onView(ViewMatchers.isRoot()).perform(object : ViewAction {
override fun getConstraints(): Matcher<View> = ViewMatchers.isRoot()
override fun getDescription(): String = "wait for $delay milliseconds"
override fun perform(uiController: UiController, v: View?) {
uiController.loopMainThreadForAtLeast(delay)
}
})
}
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
fun checkToastMessage(message: String) { fun checkToastMessage(message: String) {
Espresso.onView(ViewMatchers.withText(message)).inRoot(withDecorView(Matchers.not(decorView))) Espresso.onView(ViewMatchers.withText(message)).inRoot(withDecorView(Matchers.not(decorView)))
@@ -95,7 +87,7 @@ open class BaseTest<A : Activity>(
fun navigateBack() = Espresso.pressBack() fun navigateBack() = Espresso.pressBack()
fun addRandomShifts() { fun addRandomShifts() {
testApp.addShiftsToDatabase(getShifts()) testApp.addShiftsToDatabase(generateShifts())
} }
fun clearDataBase() = testApp.clearDatabase() fun clearDataBase() = testApp.clearDatabase()

View File

@@ -0,0 +1,55 @@
package com.appttude.h_mal.farmr.ui.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.hasTextColor
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.BaseRecyclerAdapter.CurrentViewHolder
import com.appttude.h_mal.farmr.ui.BaseTestRobot
import org.hamcrest.core.AllOf.allOf
import org.hamcrest.core.IsNot.not
fun calendarScreen(func: CalendarScreenRobot.() -> Unit) = CalendarScreenRobot().apply { func() }
class CalendarScreenRobot : BaseTestRobot() {
fun clickOnListItemWithText(text: String) =
clickOnRecyclerItemWithText<CurrentViewHolder>(R.id.shifts_available_recycler, text)
fun clickOnListItemAtPosition(position: Int) =
clickRecyclerAtPosition<CurrentViewHolder>(R.id.shifts_available_recycler, position)
fun clickOnEditForItem(position: Int) {
clickViewInRecyclerAtPosition<CurrentViewHolder>(
R.id.shifts_available_recycler,
position,
R.id.imageView
)
onView(withId(R.id.update)).perform(click())
}
fun clickOnDeleteForItem(position: Int) {
clickViewInRecyclerAtPosition<CurrentViewHolder>(
R.id.shifts_available_recycler,
position,
R.id.imageView
)
onView(withId(R.id.delete)).perform(click())
}
fun clickOnCalendarDay(day: Int) {
onView(
allOf(
withId(R.id.dayLabel),
not(hasTextColor(com.applandeo.materialcalendarview.R.color.nextMonthDayColor)),
withText("$day"),
isDisplayed()
)
).perform(click())
}
fun clickNextMonth() = clickButton(com.applandeo.materialcalendarview.R.id.forwardButton)
fun clickPreviousMonth() = clickButton(com.applandeo.materialcalendarview.R.id.previousButton)
}

View File

@@ -0,0 +1,33 @@
package com.appttude.h_mal.farmr.ui.robots
import androidx.test.espresso.action.ViewActions.scrollTo
import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.model.ShiftType
import com.appttude.h_mal.farmr.ui.BaseTestRobot
fun furtherInfoScreen(func: FurtherInfoScreenRobot.() -> Unit) = FurtherInfoScreenRobot().apply { func() }
class FurtherInfoScreenRobot : BaseTestRobot() {
fun assertShiftType(type: ShiftType) {
matchText(R.id.details_shift, type.type)
}
fun assertDescription(details: String) = matchText(R.id.details_desc, details)
fun assertDate(date: String) = matchText(R.id.details_date, date)
fun assertTime(time: String) = matchText(R.id.details_time, time)
fun assertBreak(breakSummary: String) = matchText(R.id.details_breaks, breakSummary)
fun assertDuration(duration: String) = matchText(R.id.details_duration, duration)
fun assertUnits(units: String) = fillEditText(R.id.details_units, units)
fun assertRateOfPay(rateOfPay: String) = matchText(R.id.details_pay_rate, rateOfPay)
fun assertTotalPay(text: String?) = fillEditText(R.id.details_totalpay, text)
fun update() {
matchView(R.id.details_edit).perform(scrollTo())
clickButton(R.id.details_edit)
}
}

View File

@@ -1,7 +1,9 @@
package com.appttude.h_mal.farmr.ui.robots package com.appttude.h_mal.farmr.ui.robots
import androidx.test.espresso.Espresso
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.matcher.ViewMatchers
import com.appttude.h_mal.farmr.R import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.BaseRecyclerAdapter.CurrentViewHolder
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.ui.BaseTestRobot import com.appttude.h_mal.farmr.ui.BaseTestRobot
@@ -9,9 +11,6 @@ import com.appttude.h_mal.farmr.ui.BaseTestRobot
fun homeScreen(func: HomeScreenRobot.() -> Unit) = HomeScreenRobot().apply { func() } fun homeScreen(func: HomeScreenRobot.() -> Unit) = HomeScreenRobot().apply { func() }
class HomeScreenRobot : BaseTestRobot() { class HomeScreenRobot : BaseTestRobot() {
fun clickOnItemWithText(text: String) = clickOnRecyclerItemWithText<CurrentViewHolder>(R.id.list_item_view, text)
fun clickOnItemAtPosition(position: Int) = clickRecyclerAtPosition<CurrentViewHolder>(R.id.list_item_view, position)
fun clickOnEdit(position: Int) = clickViewInRecyclerAtPosition<CurrentViewHolder>(R.id.list_item_view, position, R.id.imageView)
fun clickFab() = clickButton(R.id.fab1) fun clickFab() = clickButton(R.id.fab1)
fun clickOnInfoIcon() = clickButton(R.id.action_favorite) fun clickOnInfoIcon() = clickButton(R.id.action_favorite)
fun clickFilterInMenu() = clickOnMenuItem(R.string.filter) fun clickFilterInMenu() = clickOnMenuItem(R.string.filter)
@@ -25,4 +24,16 @@ class HomeScreenRobot : BaseTestRobot() {
val orderLabel = order.label val orderLabel = order.label
clickDialogButton(orderLabel) clickDialogButton(orderLabel)
} }
fun clickTab(tab: Tab) {
val id = when (tab) {
Tab.LIST -> R.id.nav_list
Tab.CALENDAR -> R.id.nav_calendar
}
Espresso.onView(ViewMatchers.withId(id)).perform(ViewActions.click())
}
enum class Tab {
LIST, CALENDAR
}
} }

View File

@@ -0,0 +1,54 @@
package com.appttude.h_mal.farmr.ui.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.hasChildCount
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.BaseRecyclerAdapter.CurrentViewHolder
import com.appttude.h_mal.farmr.ui.BaseTestRobot
import com.appttude.h_mal.farmr.ui.utils.EspressoHelper
import com.appttude.h_mal.farmr.ui.utils.EspressoHelper.waitFor
fun listScreen(func: ListScreenRobot.() -> Unit) = ListScreenRobot().apply { func() }
class ListScreenRobot : BaseTestRobot() {
fun clickOnItemWithText(text: String) =
clickOnRecyclerItemWithText<CurrentViewHolder>(R.id.list_item_view, text)
fun clickOnItemAtPosition(position: Int) =
clickRecyclerAtPosition<CurrentViewHolder>(R.id.list_item_view, position)
fun clickOnEditForItem(position: Int) {
clickViewInRecyclerAtPosition<CurrentViewHolder>(
R.id.list_item_view,
position,
R.id.imageView
)
waitFor(800)
EspressoHelper.waitForView(withText("Update Shift")).perform(click())
}
fun clickOnDeleteForItem(position: Int) {
clickViewInRecyclerAtPosition<CurrentViewHolder>(
R.id.list_item_view,
position,
R.id.imageView
)
waitFor(800)
EspressoHelper.waitForView(withText("Delete Shift")).perform(click())
}
fun confirmDeleteItemOnDialog() {
onView(withText("delete"))
.inRoot(isDialog())
.check(matches(isDisplayed()))
.perform(click())
}
fun assertListCount(count: Int) =
matchView(R.id.list_item_view).check(matches(hasChildCount(count)))
}

View File

@@ -23,9 +23,9 @@ class ViewItemScreenRobot : BaseTestRobot() {
matchText(R.id.details_time, "$timeIn-$timeOut") matchText(R.id.details_time, "$timeIn-$timeOut")
} }
fun matchBreakTime(mins: Int) = matchText(R.id.details_breaks, mins.toString()) fun matchBreakTime(mins: Int) = matchText(R.id.details_breaks, "$mins mins")
fun matchUnits(units: Float) = fillEditText(R.id.details_units, units.toString()) fun matchUnits(units: Float) = matchText(R.id.details_units, units.toString())
fun matchRateOfPay(rateOfPay: Float) = fillEditText(R.id.details_pay_rate, rateOfPay.toString()) fun matchRateOfPay(rateOfPay: Float) = matchText(R.id.details_pay_rate, rateOfPay.toString())
fun matchTotalPay(pay: String) = matchText(R.id.details_totalpay, pay) fun matchTotalPay(pay: String) = matchText(R.id.details_totalpay, pay)
fun matchDuration(duration: String) = matchText(R.id.details_duration, duration) fun matchDuration(duration: String) = matchText(R.id.details_duration, duration)

View File

@@ -0,0 +1,31 @@
package com.appttude.h_mal.farmr.ui.tests
import com.appttude.h_mal.farmr.ui.BaseTest
import com.appttude.h_mal.farmr.ui.MainActivity
import com.appttude.h_mal.farmr.ui.robots.homeScreen
import org.junit.Ignore
import org.junit.Test
@Ignore
class DummyShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
override fun afterLaunch() {
super.afterLaunch()
addRandomShifts()
// Content resolver hard to mock
// Dirty technique to have a populated list
homeScreen {
clickFab()
navigateBack()
}
}
// Add a shift successfully
@Test
fun openAddScreen_addNewHourlyShift_assertShiftDetail() {
homeScreen {
clickFab()
}
}
}

View File

@@ -5,11 +5,17 @@ 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.ui.BaseTest import com.appttude.h_mal.farmr.ui.BaseTest
import com.appttude.h_mal.farmr.ui.MainActivity import com.appttude.h_mal.farmr.ui.MainActivity
import com.appttude.h_mal.farmr.ui.robots.HomeScreenRobot
import com.appttude.h_mal.farmr.ui.robots.addScreen import com.appttude.h_mal.farmr.ui.robots.addScreen
import com.appttude.h_mal.farmr.ui.robots.calendarScreen
import com.appttude.h_mal.farmr.ui.robots.filterScreen import com.appttude.h_mal.farmr.ui.robots.filterScreen
import com.appttude.h_mal.farmr.ui.robots.homeScreen import com.appttude.h_mal.farmr.ui.robots.homeScreen
import com.appttude.h_mal.farmr.ui.robots.listScreen
import com.appttude.h_mal.farmr.ui.robots.viewScreen import com.appttude.h_mal.farmr.ui.robots.viewScreen
import org.junit.Test import org.junit.Test
import java.util.Calendar
import java.util.Calendar.MONTH
import java.util.Calendar.YEAR
class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) { class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
@@ -33,7 +39,7 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
// Add a shift successfully // Add a shift successfully
@Test @Test
fun openAddScreen_addNewShift_newShiftCreated() { fun openAddScreen_addNewHourlyShift_assertShiftDetail() {
homeScreen { homeScreen {
clickFab() clickFab()
} }
@@ -49,16 +55,25 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
assertTotalPay("£20.00") assertTotalPay("£20.00")
submit() submit()
} }
homeScreen { listScreen {
clickOnItemWithText("This is a description") clickOnItemWithText("This is a description")
} }
viewScreen {
matchDescription("This is a description")
matchDate("2023-02-11")
matchShiftType(ShiftType.HOURLY)
matchTime("12:00", "14:30")
matchBreakTime(30)
matchRateOfPay(10.0f)
matchDuration("2 Hours 0 Minutes (+ 30 minutes break)")
matchTotalPay("2.0 Hours @ £10.00 per Hour\nEquals: £20.00")
}
} }
// Edit a shift successfully
@Test @Test
fun test2() { fun editShift_newDetailsAdded_assertShiftDetail() {
homeScreen { listScreen {
clickOnEdit(0) clickOnEditForItem(0)
} }
addScreen { addScreen {
setDescription("Edited this shift") setDescription("Edited this shift")
@@ -70,7 +85,7 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
assertTotalPay("£40.00") assertTotalPay("£40.00")
submit() submit()
} }
homeScreen { listScreen {
clickOnItemWithText("Edited this shift") clickOnItemWithText("Edited this shift")
} }
viewScreen { viewScreen {
@@ -80,12 +95,13 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
} }
} }
// filter the list with date from
@Test @Test
fun test3() { fun applySort_listIsSorted_assertShiftsSortedCorrectly() {
homeScreen { homeScreen {
applySort(Sortable.TYPE, Order.DESCENDING) applySort(Sortable.TYPE, Order.DESCENDING)
clickOnItemAtPosition(0) listScreen {
clickOnItemAtPosition(0)
}
viewScreen { viewScreen {
matchDescription("Day five") matchDescription("Day five")
matchShiftType(ShiftType.PIECE) matchShiftType(ShiftType.PIECE)
@@ -93,25 +109,41 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
} }
} }
// filter the list with date to
@Test @Test
fun test4() { fun applyDateBetweenFilterAndClear_listIsFilteredByDate_assertFilteredResultsCorrectly() {
homeScreen { homeScreen {
clickFilterInMenu() clickFilterInMenu()
} }
filterScreen { filterScreen {
setDateIn(2023,8,3) val calendar = Calendar.getInstance()
setDateOut(2023,8,6) val year = calendar.get(YEAR)
val month = calendar.get(MONTH) + 1
setDateIn(year, month, 3)
setDateOut(year, month, 6)
submit() submit()
} }
homeScreen { listScreen {
clickOnItemAtPosition(0) assertListCount(4)
homeScreen {
clickClearFilterInMenu()
assertListCount(8)
clickFilterInMenu()
}
}
filterScreen {
val calendar = Calendar.getInstance()
val year = calendar.get(YEAR)
val month = calendar.get(MONTH) + 1
setDateOut(year, month, 6)
submit()
}
listScreen {
assertListCount(5)
} }
} }
// Add a shift as piece rate
@Test @Test
fun test5() { fun openAddScreen_addNewPieceShift_assertShiftDetail() {
homeScreen { homeScreen {
clickFab() clickFab()
} }
@@ -124,18 +156,42 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
assertTotalPay("£10.00") assertTotalPay("£10.00")
submit() submit()
} }
homeScreen { listScreen {
clickOnItemWithText("This is a description") clickOnItemWithText("This is a description")
} }
viewScreen {
matchDescription("This is a description")
matchDate("2023-02-11")
matchShiftType(ShiftType.PIECE)
matchUnits(1f)
matchRateOfPay(10.0f)
matchTotalPay("1.0 Units @ £10.00 per Unit\nEquals: £10.00")
}
} }
// Validate the details screen
@Test @Test
fun test6() { fun openCalendarTab_clickOnFirstActiveDay_assertShiftDetails() {
homeScreen {
clickTab(HomeScreenRobot.Tab.CALENDAR)
}
calendarScreen {
clickOnCalendarDay(1)
clickOnListItemAtPosition(0)
}
viewScreen {
matchDate("2023-09-01")
}
} }
// filter, sort, order and then reset
@Test @Test
fun test7() { fun deleteShift_confirmDelete_assertShiftDeleted() {
listScreen {
clickOnDeleteForItem(0)
confirmDeleteItemOnDialog()
clickOnItemAtPosition(0)
}
viewScreen {
matchDescription("Day two")
}
} }
} }

View File

@@ -2,6 +2,13 @@ package com.appttude.h_mal.farmr.ui.utils
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.utils.DATE_FORMAT
import com.appttude.h_mal.farmr.utils.TIME_FORMAT
import com.appttude.h_mal.farmr.utils.getTimeString
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Calendar.DAY_OF_MONTH
import java.util.Locale
fun getShifts() = listOf( fun getShifts() = listOf(
Shift( Shift(
@@ -100,4 +107,113 @@ fun getShifts() = listOf(
10f, 10f,
10f 10f
) )
) )
fun Calendar.setDayAndGetDateString(day: Int): String {
set(Calendar.DAY_OF_MONTH, day)
val format = SimpleDateFormat(DATE_FORMAT, Locale.getDefault())
return format.format(time)
}
fun generateShifts(): List<Shift> {
val calendar: Calendar = Calendar.getInstance()
return listOf(
Shift(
ShiftType.HOURLY,
"Day one",
calendar.setDayAndGetDateString(1),
"12:00",
"13:00",
1f,
0,
0f,
10f,
10f
),
Shift(
ShiftType.HOURLY,
"Day two",
calendar.setDayAndGetDateString(2),
"12:00",
"13:00",
1f,
0,
0f,
10f,
10f
),
Shift(
ShiftType.HOURLY,
"Day three",
calendar.setDayAndGetDateString(3),
"12:00",
"13:00",
1f,
30,
0f,
10f,
5f
),
Shift(
ShiftType.HOURLY,
"Day four",
calendar.setDayAndGetDateString(4),
"12:00",
"13:00",
1f,
30,
0f,
10f,
5f
),
Shift(
ShiftType.PIECE,
"Day five",
calendar.setDayAndGetDateString(5),
"",
"",
0f,
0,
1f,
10f,
10f
),
Shift(
ShiftType.PIECE,
"Day six",
calendar.setDayAndGetDateString(6),
"",
"",
0f,
0,
1f,
10f,
10f
),
Shift(
ShiftType.PIECE,
"Day seven",
calendar.setDayAndGetDateString(7),
"",
"",
0f,
0,
1f,
10f,
10f
),
Shift(
ShiftType.PIECE,
"Day eight",
calendar.setDayAndGetDateString(8),
"",
"",
0f,
0,
1f,
10f,
10f
)
)
}

View File

@@ -4,11 +4,13 @@ import android.os.SystemClock.sleep
import android.view.View import android.view.View
import android.widget.CheckBox import android.widget.CheckBox
import android.widget.Checkable import android.widget.Checkable
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewInteraction import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.util.TreeIterables import androidx.test.espresso.util.TreeIterables
import org.hamcrest.BaseMatcher import org.hamcrest.BaseMatcher
@@ -120,4 +122,14 @@ object EspressoHelper {
throw Exception("Error finding a view matching $viewMatcher") throw Exception("Error finding a view matching $viewMatcher")
} }
fun waitFor(delay: Long) {
onView(isRoot()).perform(object : ViewAction {
override fun getConstraints(): Matcher<View> = isRoot()
override fun getDescription(): String = "wait for $delay milliseconds"
override fun perform(uiController: UiController, v: View?) {
uiController.loopMainThreadForAtLeast(delay)
}
})
}
} }

View File

@@ -29,13 +29,6 @@ abstract class BaseFragment<V : BaseViewModel>(@LayoutRes contentLayoutId: Int)
var mActivity: BaseActivity? = null var mActivity: BaseActivity? = null
private var shortAnimationDuration by Delegates.notNull<Int>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
shortAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
mActivity = requireActivity() as BaseActivity mActivity = requireActivity() as BaseActivity

View File

@@ -12,7 +12,7 @@ import com.appttude.h_mal.farmr.utils.show
abstract class BaseListAdapter<T : Any>( abstract class BaseListAdapter<T : Any>(
diff: DiffUtil.ItemCallback<T>, diff: DiffUtil.ItemCallback<T>,
private val layoutId: Int, private val layoutId: Int,
private val emptyView: View private val emptyView: View?
) : ListAdapter<T, BaseListAdapter.CurrentViewHolder>(diff) { ) : ListAdapter<T, BaseListAdapter.CurrentViewHolder>(diff) {
override fun onCreateViewHolder( override fun onCreateViewHolder(
@@ -49,8 +49,8 @@ abstract class BaseListAdapter<T : Any>(
} }
fun checkEmpty() { fun checkEmpty() {
if (itemCount == 0) emptyView.show() if (itemCount == 0) emptyView?.show()
else emptyView.hide() else emptyView?.hide()
} }
}) })
} }

View File

@@ -0,0 +1,73 @@
package com.appttude.h_mal.farmr.base
import android.os.Bundle
import android.view.View
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavDirections
import com.appttude.h_mal.farmr.model.ViewState
import com.appttude.h_mal.farmr.utils.getGenericClassAt
import com.appttude.h_mal.farmr.utils.navigateTo
import com.appttude.h_mal.farmr.viewmodel.ApplicationViewModelFactory
import org.kodein.di.KodeinAware
import org.kodein.di.android.x.kodein
import org.kodein.di.generic.instance
import java.io.IOException
@Suppress("EmptyMethod")
abstract class ChildFragment<V : BaseViewModel>(@LayoutRes contentLayoutId: Int) :
Fragment(contentLayoutId), KodeinAware {
override val kodein by kodein()
private val factory by instance<ApplicationViewModelFactory>()
lateinit var viewModel: V
private val parent by lazy { requireParentFragment().requireParentFragment() }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel =
ViewModelProvider(parent, factory)[getGenericClassAt<V>(0).java]
configureObserver()
}
private fun configureObserver() {
viewModel.uiState.observe(viewLifecycleOwner) {
when (it) {
is ViewState.HasStarted -> onStarted()
is ViewState.HasData<*> -> onSuccess(it.data)
is ViewState.HasError<*> -> onFailure(it.error)
}
}
}
/**
* Called in case of starting operation liveData in viewModel
*/
open fun onStarted() {}
/**
* Called in case of success or some data emitted from the liveData in viewModel
*/
open fun onSuccess(data: Any?) {}
/**
* Called in case of failure or some error emitted from the liveData in viewModel
*/
open fun onFailure(error: Any?) {}
fun navigateParent(navArg: Any) {
when(navArg) {
is Int -> (parent).navigateTo(navArg)
is NavDirections -> (parent).navigateTo(navArg)
else -> { throw IOException("${navArg::class} is not a valid navigation argment") }
}
}
fun setTitle(title: String) {
(parent as BaseFragment<*>).setTitle(title)
}
}

View File

@@ -20,5 +20,5 @@ interface Repository {
timeIn: String?, timeIn: String?,
timeOut: String?, timeOut: String?,
type: String? type: String?
) ): Boolean
} }

View File

@@ -64,8 +64,8 @@ class RepositoryImpl(
timeIn: String?, timeIn: String?,
timeOut: String?, timeOut: String?,
type: String? type: String?
) { ): Boolean {
preferenceProvider.saveFilteringDetails(description, timeIn, timeOut, type) return preferenceProvider.saveFilteringDetails(description, timeIn, timeOut, type)
} }
} }

View File

@@ -44,13 +44,13 @@ class PreferenceProvider(
timeIn: String?, timeIn: String?,
timeOut: String?, timeOut: String?,
type: String? type: String?
) { ): Boolean {
preference.edit() return preference.edit()
.putString(DESCRIPTION, description) .putString(DESCRIPTION, description)
.putString(DATE_IN, timeIn) .putString(DATE_IN, timeIn)
.putString(DATE_OUT, timeOut) .putString(DATE_OUT, timeOut)
.putString(TYPE, type) .putString(TYPE, type)
.apply() .commit()
} }
fun getFilteringDetails(): Map<String, String?> { fun getFilteringDetails(): Map<String, String?> {

View File

@@ -0,0 +1,54 @@
package com.appttude.h_mal.farmr.ui
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.applandeo.materialcalendarview.CalendarView
import com.applandeo.materialcalendarview.EventDay
import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.ChildFragment
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
import com.appttude.h_mal.farmr.utils.tryGet
import com.appttude.h_mal.farmr.viewmodel.MainViewModel
import java.util.Calendar
class CalendarFragment : ChildFragment<MainViewModel>(R.layout.fragment_calendar) {
private lateinit var shiftListView: RecyclerView
private lateinit var calendarView: CalendarView
private lateinit var mAdapter: ShiftListAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
shiftListView = view.findViewById(R.id.shifts_available_recycler)
calendarView = view.findViewById(R.id.calendarView)
mAdapter = ShiftListAdapter(this, null, viewModel)
shiftListView.adapter = mAdapter
calendarView.setOnDayClickListener { populateShiftListsForDay(it.calendar) }
}
override fun onResume() {
super.onResume()
viewModel.refreshLiveData()
}
override fun onSuccess(data: Any?) {
super.onSuccess(data)
if (data is List<*>) {
val events: List<EventDay>? = viewModel.retrieveEvents()
calendarView.setEvents(events)
tryGet { calendarView.firstSelectedDate }?.let {
populateShiftListsForDay(it)
}
}
}
private fun populateShiftListsForDay(calendar: Calendar) {
val data: List<ShiftObject>? = viewModel.getShiftsOnTheDay(calendar)
mAdapter.submitList(data)
}
}

View File

@@ -131,6 +131,13 @@ class FragmentAddItem : FormFragment<SubmissionViewModel>(R.layout.fragment_add_
} }
} }
requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressed) requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressed)
id = try {
FragmentAddItemArgs.fromBundle(requireArguments()).shiftId
} catch (e: Exception) {
Log.i("Nav Args", "Failed to retrieve args from navigation")
null
}
} }
override fun onResume() { override fun onResume() {
@@ -150,17 +157,10 @@ class FragmentAddItem : FormFragment<SubmissionViewModel>(R.layout.fragment_add_
} }
private fun setupViewAfterViewCreated() { private fun setupViewAfterViewCreated() {
val id = try {
FragmentAddItemArgs.fromBundle(requireArguments()).shiftId
} catch (e: Exception) {
Log.i("Nav Args", "Failed to retrieve args from navigation")
null
}
wholeView.hide() wholeView.hide()
// Since we are editing a shift lets load the shift data into the views // Since we are editing a shift lets load the shift data into the views
id?.let { viewModel.getCurrentShift(id) }?.run { id?.let { viewModel.getCurrentShift(it) }?.run {
mLocationEditText.setText(description) mLocationEditText.setText(description)
mDateEditText.setText(date) mDateEditText.setText(date)

View File

@@ -0,0 +1,46 @@
package com.appttude.h_mal.farmr.ui
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.ChildFragment
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
import com.appttude.h_mal.farmr.model.Success
import com.appttude.h_mal.farmr.utils.displayToast
import com.appttude.h_mal.farmr.utils.navigateTo
import com.appttude.h_mal.farmr.viewmodel.MainViewModel
import com.google.android.material.floatingactionbutton.FloatingActionButton
class FragmentList : ChildFragment<MainViewModel>(R.layout.fragment_list) {
private lateinit var productListView: RecyclerView
private lateinit var emptyView: View
private lateinit var mAdapter: ShiftListAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
emptyView = view.findViewById(R.id.empty_view)
productListView = view.findViewById(R.id.list_item_view)
mAdapter = ShiftListAdapter(this, emptyView, viewModel)
productListView.adapter = mAdapter
}
override fun onStart() {
super.onStart()
viewModel.refreshLiveData()
}
override fun onSuccess(data: Any?) {
super.onSuccess(data)
if (data is List<*>) {
@Suppress("UNCHECKED_CAST")
mAdapter.submitList(data as List<ShiftObject>)
} else if (data is Success) {
displayToast(data.successMessage)
}
}
}

View File

@@ -9,29 +9,26 @@ import android.view.MenuItem
import android.view.View import android.view.View
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.recyclerview.widget.RecyclerView import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
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.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.navigateTo import com.appttude.h_mal.farmr.utils.navigateTo
import com.appttude.h_mal.farmr.viewmodel.MainViewModel import com.appttude.h_mal.farmr.viewmodel.MainViewModel
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import java.io.File import java.io.File
import kotlin.system.exitProcess import kotlin.system.exitProcess
class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main) { class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main) {
private lateinit var productListView: RecyclerView
private lateinit var emptyView: View
private lateinit var mAdapter: ShiftListAdapter
private lateinit var onBackPressed: OnBackPressedCallback private lateinit var onBackPressed: OnBackPressedCallback
lateinit var navView: BottomNavigationView
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// Inflate the layout for this fragment // Inflate the layout for this fragment
@@ -47,7 +44,6 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main) {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
setTitle("Shift List")
onBackPressed.isEnabled = true onBackPressed.isEnabled = true
} }
@@ -55,45 +51,39 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main) {
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
onBackPressed.isEnabled = false onBackPressed.isEnabled = false
viewModel.saveBottomBarState(navView.selectedItemId)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
emptyView = view.findViewById(R.id.empty_view) navView = view.findViewById(R.id.bottom_bar)
productListView = view.findViewById(R.id.list_item_view) val navHost = childFragmentManager.findFragmentById(R.id.sub_container) as NavHostFragment
mAdapter = ShiftListAdapter(this, emptyView) { val navController = navHost.navController
viewModel.deleteShift(it) navController.setGraph(R.navigation.home_navigation)
navView.setupWithNavController(navController)
viewModel.getBottomBarState()?.let {
navView.selectedItemId = it
}
navController.addOnDestinationChangedListener { _, destination, _ ->
setTitle(destination.label.toString())
} }
productListView.adapter = mAdapter
view.findViewById<FloatingActionButton>(R.id.fab1).setOnClickListener { view.findViewById<FloatingActionButton>(R.id.fab1).setOnClickListener {
navigateTo(R.id.main_to_addItem) navigateTo(R.id.main_to_addItem)
} }
} }
override fun onStart() {
super.onStart()
viewModel.refreshLiveData()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
// Inflate the menu; this adds items to the action bar if it is present. // Inflate the menu; this adds items to the action bar if it is present.
inflater.inflate(R.menu.menu_main, menu) inflater.inflate(R.menu.menu_main, menu)
} }
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 { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.delete_all -> { R.id.delete_all -> {

View File

@@ -12,6 +12,7 @@ import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
import com.appttude.h_mal.farmr.model.ShiftType import com.appttude.h_mal.farmr.model.ShiftType
import com.appttude.h_mal.farmr.utils.CURRENCY import com.appttude.h_mal.farmr.utils.CURRENCY
import com.appttude.h_mal.farmr.utils.formatAsCurrencyString import com.appttude.h_mal.farmr.utils.formatAsCurrencyString
import com.appttude.h_mal.farmr.utils.formatToTwoDp
import com.appttude.h_mal.farmr.utils.hide import com.appttude.h_mal.farmr.utils.hide
import com.appttude.h_mal.farmr.utils.navigateTo import com.appttude.h_mal.farmr.utils.navigateTo
import com.appttude.h_mal.farmr.utils.navigateToFragment import com.appttude.h_mal.farmr.utils.navigateToFragment
@@ -104,7 +105,7 @@ class FurtherInfoFragment : BaseFragment<InfoViewModel>(R.layout.fragment_futher
unitsTV.text = units.toString() unitsTV.text = units.toString()
val paymentSummary = val paymentSummary =
StringBuilder().append(units.formatAsCurrencyString()).append(" Units @ ") StringBuilder().append(units.formatToTwoDp()).append(" Units @ ")
.append(rateOfPay.formatAsCurrencyString()).append(" per Unit").append("\n") .append(rateOfPay.formatAsCurrencyString()).append(" per Unit").append("\n")
.append("Equals: ").append(totalPay.formatAsCurrencyString()) .append("Equals: ").append(totalPay.formatAsCurrencyString())
totalPayTV.text = paymentSummary totalPayTV.text = paymentSummary

View File

@@ -2,27 +2,29 @@ package com.appttude.h_mal.farmr.ui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AlertDialog import android.app.AlertDialog
import android.os.Bundle import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.appcompat.widget.PopupMenu
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import com.appttude.h_mal.farmr.R import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.BaseListAdapter import com.appttude.h_mal.farmr.base.BaseListAdapter
import com.appttude.h_mal.farmr.base.ChildFragment
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.ShiftType import com.appttude.h_mal.farmr.model.ShiftType
import com.appttude.h_mal.farmr.utils.ID
import com.appttude.h_mal.farmr.utils.formatToTwoDpString import com.appttude.h_mal.farmr.utils.formatToTwoDpString
import com.appttude.h_mal.farmr.utils.formatToTwoDp import com.appttude.h_mal.farmr.viewmodel.MainViewModel
import com.appttude.h_mal.farmr.utils.generateView
import com.appttude.h_mal.farmr.utils.navigateTo
import com.appttude.h_mal.farmr.utils.navigateToFragment const val PIECE_ITEM = 500
const val HOURLY_ITEM = 501
class ShiftListAdapter( class ShiftListAdapter(
private val fragment: Fragment, private val fragment: ChildFragment<*>,
emptyView: View, emptyView: View?,
private val longPressCallback: (Long) -> Unit private val viewModel: MainViewModel
) : BaseListAdapter<ShiftObject>(diffCallBack, R.layout.list_item_1, emptyView) { ) : BaseListAdapter<ShiftObject>(diffCallBack, R.layout.list_item_1, emptyView) {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@@ -33,63 +35,91 @@ class ShiftListAdapter(
val descriptionTextView: TextView = view.findViewById(R.id.location) val descriptionTextView: TextView = view.findViewById(R.id.location)
val dateTextView: TextView = view.findViewById(R.id.date) val dateTextView: TextView = view.findViewById(R.id.date)
val totalPay: TextView = view.findViewById(R.id.total_pay) val totalPay: TextView = view.findViewById(R.id.total_pay)
val hoursView: TextView = view.findViewById(R.id.hours)
val h: TextView = view.findViewById(R.id.h)
val minutesView: TextView = view.findViewById(R.id.minutes)
val m: TextView = view.findViewById(R.id.m)
val editView: ImageView = view.findViewById(R.id.imageView) val editView: ImageView = view.findViewById(R.id.imageView)
h.text = "h"
m.text = "m"
val typeText: String = data.type
val descriptionText: String = data.description
val dateText: String = data.date
val totalPayText: String = data.totalPay.formatToTwoDpString()
descriptionTextView.text = descriptionText when (getItemViewType(position)) {
dateTextView.text = dateText HOURLY_ITEM -> {
totalPay.text = totalPayText val hoursView: TextView = view.findViewById(R.id.hours)
val minutesView: TextView = view.findViewById(R.id.minutes)
when (ShiftType.getEnumByType(typeText)) {
ShiftType.HOURLY -> {
val time = data.getHoursMinutesPairFromDuration() val time = data.getHoursMinutesPairFromDuration()
hoursView.text = time.first hoursView.text = time.first
minutesView.text = time.second minutesView.text = if (time.second.length == 1) "0${time.second}" else time.second
} }
ShiftType.PIECE -> { PIECE_ITEM -> {
val unitsView: TextView = view.findViewById(R.id.pieces)
val unitsText: String = data.units.toString() val unitsText: String = data.units.toString()
hoursView.text = unitsText unitsView.text = unitsText
h.text = ""
minutesView.text = ""
m.text = "pcs"
} }
} }
descriptionTextView.text = data.description
dateTextView.text = data.date
totalPay.text = data.totalPay.formatToTwoDpString()
view.setOnClickListener { view.setOnClickListener {
// Navigate to further info // Navigate to further info
val nav = FragmentMainDirections.mainToFurtherInfo(data.id) val nav = FragmentMainDirections.mainToFurtherInfo(data.id)
fragment.navigateTo(nav) fragment.navigateParent(nav)
} }
editView.setOnClickListener { editView.setOnClickListener {
// Navigate to edit //creating a popup menu
val nav = FragmentMainDirections.mainToAddItem(data.id) val popup = PopupMenu(it.context, it)
fragment.navigateTo(nav) //inflating menu from xml resource
} popup.inflate(R.menu.options_menu)
view.setOnLongClickListener {
AlertDialog.Builder(it.context) //adding click listener
.setMessage("Are you sure you want to delete") popup.setOnMenuItemClickListener { menu ->
.setPositiveButton("delete") { _, _ -> longPressCallback.invoke(data.id) } when (menu.itemId) {
.setNegativeButton("cancel") { dialog, _ -> R.id.update -> {
dialog?.dismiss() // Navigate to edit
val nav = FragmentMainDirections.mainToAddItem(data.id)
fragment.navigateParent(nav)
return@setOnMenuItemClickListener true
}
R.id.delete -> {
AlertDialog.Builder(it.context)
.setMessage("Are you sure you want to delete")
.setPositiveButton("delete") { _, _ -> viewModel.deleteShift(data.id) }
.setNegativeButton("cancel") { dialog, _ ->
dialog?.dismiss()
}
.create().show()
return@setOnMenuItemClickListener true
}
else -> return@setOnMenuItemClickListener false
} }
.create().show() }
true //displaying the popup
popup.show()
} }
} }
override fun getItemViewType(position: Int): Int {
val typeString = getItem(position).type
return when (ShiftType.getEnumByType(typeString)) {
ShiftType.HOURLY -> HOURLY_ITEM
ShiftType.PIECE -> PIECE_ITEM
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CurrentViewHolder {
val layoutId = when (viewType) {
HOURLY_ITEM -> R.layout.list_cell_hourly
PIECE_ITEM -> R.layout.list_cell_piece
else -> {
return super.onCreateViewHolder(parent, viewType)
}
}
val currentView = LayoutInflater
.from(parent.context)
.inflate(layoutId, parent, false)
return CurrentViewHolder(currentView)
}
companion object { companion object {
val diffCallBack = object : DiffUtil.ItemCallback<ShiftObject>() { val diffCallBack = object : DiffUtil.ItemCallback<ShiftObject>() {
override fun areItemsTheSame(oldItem: ShiftObject, newItem: ShiftObject): Boolean { override fun areItemsTheSame(oldItem: ShiftObject, newItem: ShiftObject): Boolean {

View File

@@ -48,6 +48,13 @@ fun String.convertDateString(format: String = DATE_FORMAT): Date? {
return formatter.parse(this) return formatter.parse(this)
} }
fun String.convertToCalendar(format: String = DATE_FORMAT): Calendar? {
val date = convertDateString(format)
val calendar = Calendar.getInstance()
calendar.time = date ?: return null
return calendar
}
/** /**
* turns "HH:mm" into an hour and minutes pair * turns "HH:mm" into an hour and minutes pair
* *

View File

@@ -20,7 +20,7 @@ fun <CLASS : Any> Any.getGenericClassAt(position: Int): KClass<CLASS> =
* var s: String? * var s: String?
* i.validate{!i.isNullOrEmpty()} { print("string is empty") } * i.validate{!i.isNullOrEmpty()} { print("string is empty") }
*/ */
inline fun<T: Any?> T.validateField(validate: (T) -> Boolean, onError: () -> Unit) { inline fun <T : Any?> T.validateField(validate: (T) -> Boolean, onError: () -> Unit) {
if (!validate.invoke(this)) { if (!validate.invoke(this)) {
onError.invoke() onError.invoke()
} }
@@ -30,9 +30,25 @@ inline fun<T: Any?> T.validateField(validate: (T) -> Boolean, onError: () -> Uni
* Returns a list of all elements sorted according to the specified comparator. In order of ascending or descending * Returns a list of all elements sorted according to the specified comparator. In order of ascending or descending
* The sort is stable. It means that equal elements preserve their order relative to each other after sorting. * The sort is stable. It means that equal elements preserve their order relative to each other after sorting.
*/ */
inline fun <T, R : Comparable<R>> Iterable<T>.sortedByOrder(order: Order = Order.ASCENDING, crossinline selector: (T) -> R?): List<T> { inline fun <T, R : Comparable<R>> Iterable<T>.sortedByOrder(
order: Order = Order.ASCENDING,
crossinline selector: (T) -> R?
): List<T> {
return when (order) { return when (order) {
Order.ASCENDING -> sortedWith(compareBy(selector)) Order.ASCENDING -> sortedWith(compareBy(selector))
Order.DESCENDING -> sortedWith(compareByDescending(selector)) Order.DESCENDING -> sortedWith(compareByDescending(selector))
} }
}
/**
* Tries to retrieve a variable that may throw an exception
*
* @Returns variable if successful else null
*/
inline fun <T : Any?> tryGet(validate: () -> T?): T? {
return try {
validate.invoke()
} catch (e: Exception) {
null
}
} }

View File

@@ -5,6 +5,7 @@ import androidx.fragment.app.Fragment
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import androidx.navigation.Navigation import androidx.navigation.Navigation
import com.appttude.h_mal.farmr.R import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.ChildFragment
fun Fragment.navigateToFragment(newFragment: Fragment) { fun Fragment.navigateToFragment(newFragment: Fragment) {
childFragmentManager.beginTransaction() childFragmentManager.beginTransaction()

View File

@@ -1,8 +1,12 @@
package com.appttude.h_mal.farmr.viewmodel package com.appttude.h_mal.farmr.viewmodel
import android.graphics.Color
import android.graphics.drawable.Drawable
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.applandeo.materialcalendarview.EventDay
import com.appttude.h_mal.farmr.R
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_BREAK
@@ -21,6 +25,7 @@ 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.model.Success
import com.appttude.h_mal.farmr.utils.convertDateString import com.appttude.h_mal.farmr.utils.convertDateString
import com.appttude.h_mal.farmr.utils.convertToCalendar
import com.appttude.h_mal.farmr.utils.formatAsCurrencyString import com.appttude.h_mal.farmr.utils.formatAsCurrencyString
import com.appttude.h_mal.farmr.utils.sortedByOrder import com.appttude.h_mal.farmr.utils.sortedByOrder
import jxl.Workbook import jxl.Workbook
@@ -30,6 +35,7 @@ import jxl.write.WritableWorkbook
import jxl.write.WriteException import jxl.write.WriteException
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.Calendar
import java.util.Locale import java.util.Locale
@@ -43,6 +49,8 @@ class MainViewModel(
private var mSort: Sortable = Sortable.ID private var mSort: Sortable = Sortable.ID
private var mOrder: Order = Order.ASCENDING private var mOrder: Order = Order.ASCENDING
private var selectedItemId: Int? = null
private val observer = Observer<List<ShiftObject>> { private val observer = Observer<List<ShiftObject>> {
it?.let { it?.let {
val result = it.applyFilters().sortList(mSort, mOrder) val result = it.applyFilters().sortList(mSort, mOrder)
@@ -93,7 +101,7 @@ class MainViewModel(
if (second == null) return compareDate.after(first) if (second == null) return compareDate.after(first)
if (first == null) return compareDate.before(second) if (first == null) return compareDate.before(second)
return compareDate.after(first) && compareDate.before(second) return compareDate.compareTo(first) * second.compareTo(compareDate) >= 0
} }
@@ -199,9 +207,8 @@ class MainViewModel(
} }
fun clearFilters() { fun clearFilters() {
super.setFiltrationDetails(null, null, null, null) val result = super.setFiltrationDetails(null, null, null, null)
onSuccess(Success("Filters have been cleared")) if (result) refreshLiveData()
refreshLiveData()
} }
fun createExcelSheet(file: File): File? { fun createExcelSheet(file: File): File? {
@@ -283,4 +290,25 @@ class MainViewModel(
return null return null
} }
fun retrieveEvents(): List<EventDay>? {
val shiftList = shiftLiveData.value ?: return null
return shiftList.applyFilters().mapNotNull {
it.date.convertToCalendar()
?.let { d -> EventDay(d, R.drawable.baseline_list_alt_24, Color.parseColor("#228B22")) }
}
}
fun getShiftsOnTheDay(calendar: Calendar): List<ShiftObject>? {
val shiftList = shiftLiveData.value ?: return null
return shiftList.filter { it.date.convertToCalendar()?.compareTo(calendar) == 0 }
}
fun saveBottomBarState(selectedItemId: Int) {
this.selectedItemId = selectedItemId
}
fun getBottomBarState(): Int? {
return selectedItemId
}
} }

View File

@@ -18,25 +18,13 @@ open class ShiftViewModel(
*/ */
fun getCurrentShift(id: Long) = repository.readSingleShiftFromDatabase(id) fun getCurrentShift(id: Long) = repository.readSingleShiftFromDatabase(id)
/**
* Lambda function that will invoke onError(...) on failure
* but update live data when successful
*/
private inline fun doTry(operation: () -> Unit) {
try {
operation.invoke()
} catch (e: Exception) {
onError(e)
}
}
open fun setFiltrationDetails( open fun setFiltrationDetails(
description: String?, description: String?,
dateFrom: String?, dateFrom: String?,
dateTo: String?, dateTo: String?,
type: String? type: String?
) { ): Boolean {
repository.setFilteringDetailsInPrefs(description, dateFrom, dateTo, type) return repository.setFilteringDetailsInPrefs(description, dateFrom, dateTo, type)
} }
open fun getFiltrationDetails(): FilterStore { open fun getFiltrationDetails(): FilterStore {

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,4h-1V2h-2v2H8V2H6v2H5C3.89,4 3.01,4.9 3.01,6L3,20c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V6C21,4.9 20.1,4 19,4zM19,20H5V10h14V20zM9,14H7v-2h2V14zM13,14h-2v-2h2V14zM17,14h-2v-2h2V14zM9,18H7v-2h2V18zM13,18h-2v-2h2V18zM17,18h-2v-2h2V18z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,5v14L5,19L5,5h14m1.1,-2L3.9,3c-0.5,0 -0.9,0.4 -0.9,0.9v16.2c0,0.4 0.4,0.9 0.9,0.9h16.2c0.4,0 0.9,-0.5 0.9,-0.9L21,3.9c0,-0.5 -0.5,-0.9 -0.9,-0.9zM11,7h6v2h-6L11,7zM11,11h6v2h-6v-2zM11,15h6v2h-6zM7,7h2v2L7,9zM7,11h2v2L7,13zM7,15h2v2L7,17z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

@@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#656565"
android:orientation="horizontal"
android:paddingTop="24dp"
android:paddingBottom="24dp"
android:showDividers="end">
<LinearLayout
android:id="@+id/time_holder"
android:layout_width="@dimen/unit_holder_width"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginStart="12dp"
android:gravity="end"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/hours"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="00"
android:textColor="#143d66"
android:textSize="@dimen/unit_text_size" />
<TextView
android:id="@+id/h"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hours_symbol"
android:textColor="#143d66"
android:textSize="@dimen/units_symbol_size" />
<TextView
android:id="@+id/minutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="00"
android:textColor="#143d66"
android:textSize="@dimen/unit_text_size" />
<TextView
android:id="@+id/m"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/minutes_symbol"
android:textColor="#143d66"
android:textSize="@dimen/units_symbol_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/totalpay_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal"
app:layout_constraintRight_toRightOf="@id/time_holder"
app:layout_constraintTop_toBottomOf="@id/time_holder">
<TextView
android:id="@+id/currency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pound_sign"
android:textColor="#728fcc"
android:textSize="@dimen/units_symbol_size" />
<TextView
android:id="@+id/total_pay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="000.00"
android:textColor="#728fcc"
android:textSize="@dimen/total_pay_size" />
</LinearLayout>
<View
android:id="@+id/line"
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:background="@android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="@id/totalpay_holder"
app:layout_constraintLeft_toRightOf="@id/time_holder"
app:layout_constraintTop_toTopOf="@id/time_holder" />
<TextView
android:id="@+id/location"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:gravity="bottom|start"
android:textColor="#000000"
android:textSize="@dimen/location_size"
android:autoSizeMinTextSize="@dimen/location_autosize_min"
android:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@id/time_holder"
app:layout_constraintLeft_toRightOf="@id/line"
app:layout_constraintRight_toLeftOf="@id/imageView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
android:maxLines="2"
tools:text="Location Name is quite long and with a second line and more" />
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:maxLines="3"
tools:text="01-05-2010"
android:textSize="@dimen/date_size"
android:gravity="center"
app:layout_constraintTop_toBottomOf="@id/location"
app:layout_constraintLeft_toLeftOf="@id/location"
app:layout_constraintBottom_toBottomOf="@id/totalpay_holder"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/baseline_more_vert_24"
android:layout_marginEnd="12dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#656565"
android:orientation="horizontal"
android:paddingTop="24dp"
android:paddingBottom="24dp"
android:showDividers="end">
<LinearLayout
android:id="@+id/time_holder"
android:layout_width="@dimen/unit_holder_width"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginStart="12dp"
android:gravity="end"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/pieces"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="10.0"
android:textColor="#143d66"
android:textSize="@dimen/unit_text_size" />
<TextView
android:id="@+id/h"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/piece_symbol"
android:textColor="#143d66"
android:textSize="@dimen/units_symbol_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/totalpay_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal"
app:layout_constraintRight_toRightOf="@id/time_holder"
app:layout_constraintTop_toBottomOf="@id/time_holder">
<TextView
android:id="@+id/currency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pound_sign"
android:textColor="#728fcc"
android:textSize="@dimen/units_symbol_size" />
<TextView
android:id="@+id/total_pay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="000.00"
android:textColor="#728fcc"
android:textSize="@dimen/total_pay_size" />
</LinearLayout>
<View
android:id="@+id/line"
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:background="@android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="@id/totalpay_holder"
app:layout_constraintLeft_toRightOf="@id/time_holder"
app:layout_constraintTop_toTopOf="@id/time_holder" />
<TextView
android:id="@+id/location"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:gravity="bottom|start"
android:textColor="#000000"
android:textSize="@dimen/location_size"
android:autoSizeMinTextSize="@dimen/location_autosize_min"
android:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@id/time_holder"
app:layout_constraintLeft_toRightOf="@id/line"
app:layout_constraintRight_toLeftOf="@id/imageView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
android:maxLines="2"
tools:text="Location Name is quite long and with a second line" />
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:maxLines="3"
tools:text="01-05-2010"
android:textSize="@dimen/date_size"
android:gravity="center"
app:layout_constraintTop_toBottomOf="@id/location"
app:layout_constraintLeft_toLeftOf="@id/location"
app:layout_constraintBottom_toBottomOf="@id/totalpay_holder"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/baseline_more_vert_24"
android:layout_marginEnd="12dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".ui.CalendarFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin">
<com.applandeo.materialcalendarview.CalendarView
android:id="@+id/calendarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:headerColor="@color/colorPrimary"
app:highlightedDaysLabelsColor="@color/colorPrimary"
app:selectionColor="@color/colorAccent"
app:type="classic" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/shifts_available_recycler"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_horizontal_margin"
android:nestedScrollingEnabled="false"
tools:itemCount="5"
tools:listitem="@layout/list_cell_hourly" />
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,24 @@
<RelativeLayout 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"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.appttude.h_mal.farmr.ui.FragmentList">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_item_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/list_cell_hourly">
</androidx.recyclerview.widget.RecyclerView>
<include
android:layout_centerInParent="true"
android:visibility="visible"
layout="@layout/empty_list_view"
android:id="@+id/empty_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>

View File

@@ -1,34 +1,39 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.appttude.h_mal.farmr.ui.FragmentMain"> tools:context="com.appttude.h_mal.farmr.ui.FragmentMain">
<androidx.recyclerview.widget.RecyclerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/list_item_view" android:id="@+id/sub_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintBottom_toTopOf="@id/bottom_bar"
tools:listitem="@layout/list_item_1"> app:layout_constraintLeft_toLeftOf="parent"
</androidx.recyclerview.widget.RecyclerView> app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:layout="@layout/fragment_calendar" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_navigation_menu" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab1" android:id="@+id/fab1"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentRight="true" app:layout_constraintRight_toRightOf="parent"
android:layout_alignParentBottom="true" app:layout_constraintBottom_toTopOf="@id/bottom_bar"
android:layout_margin="@dimen/fab_margin" android:layout_margin="@dimen/fab_margin"
android:contentDescription="@string/fab"
android:src="@drawable/add" android:src="@drawable/add"
app:backgroundTint="@color/colorPrimary" /> app:backgroundTint="@color/colorPrimary" />
<include </androidx.constraintlayout.widget.ConstraintLayout>
android:layout_centerInParent="true"
android:visibility="visible"
layout="@layout/empty_list_view"
android:id="@+id/empty_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>

View File

@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#656565"
android:orientation="horizontal"
android:paddingTop="24dp"
android:paddingBottom="24dp"
android:showDividers="end">
<LinearLayout
android:id="@+id/time_holder"
android:layout_width="@dimen/unit_holder_width"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginStart="12dp"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/hours"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="00"
android:textColor="#143d66"
android:textSize="@dimen/unit_text_size" />
<TextView
android:id="@+id/h"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hours_symbol"
android:textColor="#143d66"
android:textSize="@dimen/units_symbol_size" />
<TextView
android:id="@+id/minutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="00"
android:textColor="#143d66"
android:textSize="@dimen/unit_text_size" />
<TextView
android:id="@+id/m"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/minutes_symbol"
android:textColor="#143d66"
android:textSize="@dimen/units_symbol_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/totalpay_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal"
app:layout_constraintRight_toRightOf="@id/time_holder"
app:layout_constraintTop_toBottomOf="@id/time_holder">
<TextView
android:id="@+id/currency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pound_sign"
android:textColor="#728fcc"
android:textSize="@dimen/units_symbol_size" />
<TextView
android:id="@+id/total_pay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="000.00"
android:textColor="#728fcc"
android:textSize="@dimen/total_pay_size" />
</LinearLayout>
<View
android:id="@+id/line"
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:background="@android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="@id/totalpay_holder"
app:layout_constraintLeft_toRightOf="@id/time_holder"
app:layout_constraintTop_toTopOf="@id/time_holder" />
<TextView
android:id="@+id/location"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:gravity="bottom|start"
android:textColor="#000000"
android:textSize="@dimen/location_size"
app:layout_constraintBottom_toBottomOf="@id/time_holder"
app:layout_constraintLeft_toRightOf="@id/line"
app:layout_constraintRight_toLeftOf="@id/imageView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
android:maxLines="1"
tools:text="Location Name" />
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:maxLines="3"
tools:text="01-05-2010"
android:textSize="@dimen/date_size"
android:gravity="center"
app:layout_constraintTop_toBottomOf="@id/location"
app:layout_constraintLeft_toLeftOf="@id/location"
app:layout_constraintBottom_toBottomOf="@id/totalpay_holder"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/baseline_more_vert_24"
android:layout_marginEnd="12dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#656565"
android:orientation="horizontal"
android:paddingTop="24dp"
android:paddingBottom="24dp"
android:showDividers="end">
<LinearLayout
android:id="@+id/time_holder"
android:layout_width="@dimen/unit_holder_width"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginStart="12dp"
android:gravity="end"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/pieces"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="10.0"
android:textColor="#143d66"
android:textSize="@dimen/unit_text_size" />
<TextView
android:id="@+id/h"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/piece_symbol"
android:textColor="#143d66"
android:textSize="@dimen/units_symbol_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/totalpay_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal"
app:layout_constraintRight_toRightOf="@id/time_holder"
app:layout_constraintTop_toBottomOf="@id/time_holder">
<TextView
android:id="@+id/currency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pound_sign"
android:textColor="#728fcc"
android:textSize="@dimen/units_symbol_size" />
<TextView
android:id="@+id/total_pay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="000.00"
android:textColor="#728fcc"
android:textSize="@dimen/total_pay_size" />
</LinearLayout>
<View
android:id="@+id/line"
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:background="@android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="@id/totalpay_holder"
app:layout_constraintLeft_toRightOf="@id/time_holder"
app:layout_constraintTop_toTopOf="@id/time_holder" />
<TextView
android:id="@+id/location"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:gravity="bottom|start"
android:textColor="#000000"
android:textSize="@dimen/location_size"
app:layout_constraintBottom_toBottomOf="@id/time_holder"
app:layout_constraintLeft_toRightOf="@id/line"
app:layout_constraintRight_toLeftOf="@id/imageView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
android:maxLines="1"
tools:text="Location Name" />
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:maxLines="3"
tools:text="01-05-2010"
android:textSize="@dimen/date_size"
android:gravity="center"
app:layout_constraintTop_toBottomOf="@id/location"
app:layout_constraintLeft_toLeftOf="@id/location"
app:layout_constraintBottom_toBottomOf="@id/totalpay_holder"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/baseline_more_vert_24"
android:layout_marginEnd="12dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linearLayout" android:id="@+id/linearLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -8,10 +9,11 @@
android:orientation="horizontal" android:orientation="horizontal"
android:paddingBottom="24dp" android:paddingBottom="24dp"
android:paddingTop="24dp" android:paddingTop="24dp"
android:showDividers="end"> android:showDividers="end"
tools:ignore="HardcodedText">
<LinearLayout <LinearLayout
android:layout_width="87dp" android:layout_width="@dimen/unit_holder_width"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
@@ -30,7 +32,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="00" android:text="00"
android:textColor="#143d66" android:textColor="#143d66"
android:textSize="30sp" /> android:textSize="@dimen/unit_text_size" />
<TextView <TextView
android:id="@+id/h" android:id="@+id/h"
@@ -38,7 +40,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="h" android:text="h"
android:textColor="#143d66" android:textColor="#143d66"
android:textSize="12sp" /> android:textSize="@dimen/units_symbol_size" />
<TextView <TextView
android:id="@+id/minutes" android:id="@+id/minutes"
@@ -47,7 +49,7 @@
android:layout_weight="1" android:layout_weight="1"
android:text="00" android:text="00"
android:textColor="#143d66" android:textColor="#143d66"
android:textSize="30sp" /> android:textSize="@dimen/unit_text_size"/>
<TextView <TextView
android:id="@+id/m" android:id="@+id/m"
@@ -56,7 +58,7 @@
android:layout_weight="1" android:layout_weight="1"
android:text="m" android:text="m"
android:textColor="#143d66" android:textColor="#143d66"
android:textSize="12sp" /> android:textSize="@dimen/units_symbol_size" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@@ -70,9 +72,9 @@
android:id="@+id/currency" android:id="@+id/currency"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="£" android:text="@string/pound_sign"
android:textColor="#728fcc" android:textColor="#728fcc"
android:textSize="12sp" /> android:textSize="@dimen/units_symbol_size" />
<TextView <TextView
android:id="@+id/total_pay" android:id="@+id/total_pay"
@@ -80,7 +82,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="000.00" android:text="000.00"
android:textColor="#728fcc" android:textColor="#728fcc"
android:textSize="20sp" /> android:textSize="@dimen/total_pay_size" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@@ -106,7 +108,7 @@
android:layout_below="@+id/date" android:layout_below="@+id/date"
android:maxLines="3" android:maxLines="3"
android:text="Location Name" android:text="Location Name"
android:textSize="20sp" /> android:textSize="@dimen/location_size" />
<TextView <TextView
android:id="@+id/date" android:id="@+id/date"
@@ -119,7 +121,7 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="01-05-2010" android:text="01-05-2010"
android:textColor="#000000" android:textColor="#000000"
android:textSize="16sp" /> android:textSize="@dimen/date_size" />
<ImageView <ImageView
android:id="@+id/imageView" android:id="@+id/imageView"

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/nav_list"
app:showAsAction="always|withText"
android:icon="@drawable/baseline_list_alt_24"
android:title="@string/text_label_1"/>
<item
android:id="@+id/nav_calendar"
app:showAsAction="always|withText"
android:icon="@drawable/baseline_calendar_month_24"
android:title="@string/text_label_2"/>
</menu>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/update"
android:orderInCategory="100"
android:title="@string/update_shift"
app:showAsAction="never" />
<item
android:id="@+id/delete"
android:orderInCategory="100"
android:title="@string/delete_shift"
app:showAsAction="never" />
</menu>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/shift_navigation"
app:startDestination="@id/nav_list">
<fragment
android:id="@+id/nav_list"
android:name="com.appttude.h_mal.farmr.ui.FragmentList"
android:label="@string/text_label_1"
tools:layout="@layout/fragment_list" />
<fragment
android:id="@+id/nav_calendar"
android:name="com.appttude.h_mal.farmr.ui.CalendarFragment"
android:label="@string/text_label_2"
tools:layout="@layout/fragment_calendar" />
</navigation>

View File

@@ -12,13 +12,25 @@
tools:layout="@layout/fragment_main" > tools:layout="@layout/fragment_main" >
<action <action
android:id="@+id/main_to_addItem" android:id="@+id/main_to_addItem"
app:destination="@id/fragmentAddItem" /> app:destination="@id/fragmentAddItem"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action <action
android:id="@+id/main_to_filterData" android:id="@+id/main_to_filterData"
app:destination="@id/filterDataFragment" /> app:destination="@id/filterDataFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
<action <action
android:id="@+id/main_to_furtherInfo" android:id="@+id/main_to_furtherInfo"
app:destination="@id/furtherInfoFragment" /> app:destination="@id/furtherInfoFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
</fragment> </fragment>
<fragment <fragment
android:id="@+id/fragmentAddItem" android:id="@+id/fragmentAddItem"
@@ -41,7 +53,11 @@
tools:layout="@layout/fragment_futher_info" > tools:layout="@layout/fragment_futher_info" >
<action <action
android:id="@+id/furtherInfo_to_AddItem" android:id="@+id/furtherInfo_to_AddItem"
app:destination="@id/fragmentAddItem" /> app:destination="@id/fragmentAddItem"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
<argument <argument
android:name="shiftId" android:name="shiftId"
app:argType="long" /> app:argType="long" />

View File

@@ -4,6 +4,13 @@
<dimen name="activity_vertical_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen> <dimen name="fab_margin">16dp</dimen>
<dimen name="appbar_padding_top">8dp</dimen> <dimen name="appbar_padding_top">8dp</dimen>
<dimen name="unit_text_size">20sp</dimen>
<dimen name="units_symbol_size">12sp</dimen>
<dimen name="total_pay_size">16sp</dimen>
<dimen name="location_size">16sp</dimen>
<dimen name="date_size">14sp</dimen>
<dimen name="unit_holder_width">75dp</dimen>
<dimen name="location_autosize_min">12sp</dimen>
</resources> </resources>

View File

@@ -87,14 +87,18 @@
<string name="export">Export Data</string> <string name="export">Export Data</string>
<string name="sort">Sort</string> <string name="sort">Sort</string>
<string name="admob_unit_id">ca-app-pub-3406791512187471/7557456476</string>
<string name="banner_home_footer">ca-app-pub-3406791512187471~9541579845</string>
<string name="help">Help &amp; Support</string> <string name="help">Help &amp; Support</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="further_info_title">Shift Details</string> <string name="further_info_title">Shift Details</string>
<string name="insert_break_in_minutes">insert break in minutes</string> <string name="insert_break_in_minutes">insert break in minutes</string>
<string name="break_res">Break</string> <string name="break_res">Break</string>
<string name="hours_symbol">h</string>
<string name="minutes_symbol">m</string>
<string name="piece_symbol">pcs</string>
<string name="pound_sign">£</string>
<string name="delete_shift">Delete Shift</string>
<string name="update_shift">Update Shift</string>
<string name="fab">Floating action button</string>
<string name="text_label_1">Shifts</string>
<string name="text_label_2">Calendar</string>
</resources> </resources>

View File

@@ -90,7 +90,7 @@ class MainViewModelTest {
val retrievedShifts = retrieveCurrentData() val retrievedShifts = retrieveCurrentData()
val description = viewModel.getInformation() val description = viewModel.getInformation()
every { repository.setFilteringDetailsInPrefs(null, null, null, null) }.returns(Unit) every { repository.setFilteringDetailsInPrefs(null, null, null, null) }.returns(true)
every { repository.retrieveFilteringDetailsInPrefs() }.returns(getFilter()) every { repository.retrieveFilteringDetailsInPrefs() }.returns(getFilter())
viewModel.clearFilters() viewModel.clearFilters()
val descriptionAfterClearedFilter = viewModel.getInformation() val descriptionAfterClearedFilter = viewModel.getInformation()