Merge pull request #44 from hmalik144/master

Calendar view and tabbed layout
This commit is contained in:
2023-09-15 17:19:49 +01:00
committed by GitHub
48 changed files with 1448 additions and 206 deletions

View File

@@ -39,7 +39,7 @@ commands:
- android/start-emulator-and-run-tests:
post-emulator-launch-assemble-command: ./gradlew assembleAndroidTest
test-command: ./gradlew connectedDebugAndroidTest --continue
system-image: system-images;android-26;google_apis;x86
system-image: system-images;android-31;default;x86_64
# store screenshots for failed ui tests
- when:
condition: on_fail
@@ -120,6 +120,7 @@ workflows:
only:
- master
- release
- /ui_test.*/
- deploy-to-playstore:
context: appttude
filters:

View File

@@ -14,8 +14,8 @@ android {
applicationId "com.appttude.h_mal.farmr"
minSdkVersion MIN_SDK_VERSION
targetSdkVersion TARGET_SDK_VERSION
versionCode 6
versionName "2.4"
versionCode 7
versionName "3.0"
testInstrumentationRunner 'com.appttude.h_mal.farmr.application.TestRunner'
vectorDrawables.useSupportLibrary = true
}
@@ -88,4 +88,6 @@ dependencies {
implementation "org.kodein.di:kodein-di-framework-android-core:$KODEIN_VERSION"
/ * jxl * /
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.platform.app.InstrumentationRegistry
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 kotlinx.coroutines.runBlocking
import org.hamcrest.Matcher
@@ -73,16 +75,6 @@ open class BaseTest<A : Activity>(
open fun afterLaunch() {}
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")
fun checkToastMessage(message: String) {
Espresso.onView(ViewMatchers.withText(message)).inRoot(withDecorView(Matchers.not(decorView)))
@@ -95,7 +87,7 @@ open class BaseTest<A : Activity>(
fun navigateBack() = Espresso.pressBack()
fun addRandomShifts() {
testApp.addShiftsToDatabase(getShifts())
testApp.addShiftsToDatabase(generateShifts())
}
fun clearDataBase() = testApp.clearDatabase()

View File

@@ -23,6 +23,7 @@ import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.platform.app.InstrumentationRegistry
import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.ui.utils.EspressoHelper.waitForView
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.anything
@@ -58,6 +59,8 @@ open class BaseTestRobot {
fun matchText(resId: Int, text: String): ViewInteraction = matchText(matchView(resId), text)
fun scrollTo(viewId: Int): ViewInteraction = matchView(viewId).perform(ViewActions.scrollTo())
fun clickListItem(listRes: Int, position: Int) {
onData(anything())
.inAdapterView(allOf(withId(listRes)))

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
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.base.BaseRecyclerAdapter.CurrentViewHolder
import com.appttude.h_mal.farmr.model.Order
import com.appttude.h_mal.farmr.model.Sortable
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() }
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 clickOnInfoIcon() = clickButton(R.id.action_favorite)
fun clickFilterInMenu() = clickOnMenuItem(R.string.filter)
@@ -25,4 +24,16 @@ class HomeScreenRobot : BaseTestRobot() {
val orderLabel = order.label
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")
}
fun matchBreakTime(mins: Int) = matchText(R.id.details_breaks, mins.toString())
fun matchUnits(units: Float) = fillEditText(R.id.details_units, units.toString())
fun matchRateOfPay(rateOfPay: Float) = fillEditText(R.id.details_pay_rate, rateOfPay.toString())
fun matchBreakTime(mins: Int) = matchText(R.id.details_breaks, "$mins mins")
fun matchUnits(units: Float) = matchText(R.id.details_units, units.toString())
fun matchRateOfPay(rateOfPay: Float) = matchText(R.id.details_pay_rate, rateOfPay.toString())
fun matchTotalPay(pay: String) = matchText(R.id.details_totalpay, pay)
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

@@ -1,15 +1,25 @@
package com.appttude.h_mal.farmr.ui.tests
import androidx.test.espresso.action.ViewActions
import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.model.Order
import com.appttude.h_mal.farmr.model.ShiftType
import com.appttude.h_mal.farmr.model.Sortable
import com.appttude.h_mal.farmr.ui.BaseTest
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.calendarScreen
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.listScreen
import com.appttude.h_mal.farmr.ui.robots.viewScreen
import com.appttude.h_mal.farmr.ui.utils.EspressoHelper.waitFor
import org.junit.Ignore
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) {
@@ -33,7 +43,7 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
// Add a shift successfully
@Test
fun openAddScreen_addNewShift_newShiftCreated() {
fun openAddScreen_addNewHourlyShift_assertShiftDetail() {
homeScreen {
clickFab()
}
@@ -49,16 +59,25 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
assertTotalPay("£20.00")
submit()
}
homeScreen {
listScreen {
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
fun test2() {
homeScreen {
clickOnEdit(0)
fun editShift_newDetailsAdded_assertShiftDetail() {
listScreen {
clickOnEditForItem(0)
}
addScreen {
setDescription("Edited this shift")
@@ -70,7 +89,7 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
assertTotalPay("£40.00")
submit()
}
homeScreen {
listScreen {
clickOnItemWithText("Edited this shift")
}
viewScreen {
@@ -80,12 +99,13 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
}
}
// filter the list with date from
@Test
fun test3() {
fun applySort_listIsSorted_assertShiftsSortedCorrectly() {
homeScreen {
applySort(Sortable.TYPE, Order.DESCENDING)
clickOnItemAtPosition(0)
listScreen {
clickOnItemAtPosition(0)
}
viewScreen {
matchDescription("Day five")
matchShiftType(ShiftType.PIECE)
@@ -93,25 +113,26 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
}
}
// filter the list with date to
@Test
fun test4() {
fun applyDateBetweenFilterAndClear_listIsFilteredByDate_assertFilteredResultsCorrectly() {
homeScreen {
clickFilterInMenu()
}
filterScreen {
setDateIn(2023,8,3)
setDateOut(2023,8,6)
val calendar = Calendar.getInstance()
val year = calendar.get(YEAR)
val month = calendar.get(MONTH) + 1
setDateIn(year, month, 3)
setDateOut(year, month, 6)
submit()
}
homeScreen {
clickOnItemAtPosition(0)
listScreen {
assertListCount(4)
}
}
// Add a shift as piece rate
@Test
fun test5() {
fun openAddScreen_addNewPieceShift_assertShiftDetail() {
homeScreen {
clickFab()
}
@@ -124,18 +145,44 @@ class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
assertTotalPay("£10.00")
submit()
}
homeScreen {
listScreen {
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
@Ignore("Fails in circleci - device size")
@Test
fun test6() {
fun openCalendarTab_clickOnFirstActiveDay_assertShiftDetails() {
homeScreen {
clickTab(HomeScreenRobot.Tab.CALENDAR)
}
calendarScreen {
clickOnCalendarDay(1)
scrollTo(R.id.shifts_available_recycler)
clickOnListItemAtPosition(0)
}
viewScreen {
matchDate("2023-09-01")
}
}
// filter, sort, order and then reset
@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.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(
Shift(
@@ -100,4 +107,113 @@ fun getShifts() = listOf(
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.widget.CheckBox
import android.widget.Checkable
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.util.TreeIterables
import org.hamcrest.BaseMatcher
@@ -120,4 +122,14 @@ object EspressoHelper {
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
private var shortAnimationDuration by Delegates.notNull<Int>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
shortAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mActivity = requireActivity() as BaseActivity

View File

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

View File

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

View File

@@ -44,13 +44,13 @@ class PreferenceProvider(
timeIn: String?,
timeOut: String?,
type: String?
) {
preference.edit()
): Boolean {
return preference.edit()
.putString(DESCRIPTION, description)
.putString(DATE_IN, timeIn)
.putString(DATE_OUT, timeOut)
.putString(TYPE, type)
.apply()
.commit()
}
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)
id = try {
FragmentAddItemArgs.fromBundle(requireArguments()).shiftId
} catch (e: Exception) {
Log.i("Nav Args", "Failed to retrieve args from navigation")
null
}
}
override fun onResume() {
@@ -150,17 +157,10 @@ class FragmentAddItem : FormFragment<SubmissionViewModel>(R.layout.fragment_add_
}
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()
// 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)
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 androidx.activity.OnBackPressedCallback
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.base.BaseFragment
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
import com.appttude.h_mal.farmr.model.Order
import com.appttude.h_mal.farmr.model.Sortable
import com.appttude.h_mal.farmr.model.Success
import com.appttude.h_mal.farmr.utils.createDialog
import com.appttude.h_mal.farmr.utils.displayToast
import com.appttude.h_mal.farmr.utils.navigateTo
import com.appttude.h_mal.farmr.viewmodel.MainViewModel
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import java.io.File
import kotlin.system.exitProcess
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
lateinit var navView: BottomNavigationView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Inflate the layout for this fragment
@@ -47,7 +44,6 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main) {
override fun onResume() {
super.onResume()
setTitle("Shift List")
onBackPressed.isEnabled = true
}
@@ -55,45 +51,39 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main) {
override fun onPause() {
super.onPause()
onBackPressed.isEnabled = false
viewModel.saveBottomBarState(navView.selectedItemId)
}
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)
navView = view.findViewById(R.id.bottom_bar)
val navHost = childFragmentManager.findFragmentById(R.id.sub_container) as NavHostFragment
mAdapter = ShiftListAdapter(this, emptyView) {
viewModel.deleteShift(it)
val navController = navHost.navController
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 {
navigateTo(R.id.main_to_addItem)
}
}
override fun onStart() {
super.onStart()
viewModel.refreshLiveData()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
// Inflate the menu; this adds items to the action bar if it is present.
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 {
when (item.itemId) {
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.utils.CURRENCY
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.navigateTo
import com.appttude.h_mal.farmr.utils.navigateToFragment
@@ -104,7 +105,7 @@ class FurtherInfoFragment : BaseFragment<InfoViewModel>(R.layout.fragment_futher
unitsTV.text = units.toString()
val paymentSummary =
StringBuilder().append(units.formatAsCurrencyString()).append(" Units @ ")
StringBuilder().append(units.formatToTwoDp()).append(" Units @ ")
.append(rateOfPay.formatAsCurrencyString()).append(" per Unit").append("\n")
.append("Equals: ").append(totalPay.formatAsCurrencyString())
totalPayTV.text = paymentSummary

View File

@@ -2,27 +2,29 @@ package com.appttude.h_mal.farmr.ui
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.appcompat.widget.PopupMenu
import androidx.recyclerview.widget.DiffUtil
import com.appttude.h_mal.farmr.R
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.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.formatToTwoDp
import com.appttude.h_mal.farmr.utils.generateView
import com.appttude.h_mal.farmr.utils.navigateTo
import com.appttude.h_mal.farmr.utils.navigateToFragment
import com.appttude.h_mal.farmr.viewmodel.MainViewModel
const val PIECE_ITEM = 500
const val HOURLY_ITEM = 501
class ShiftListAdapter(
private val fragment: Fragment,
emptyView: View,
private val longPressCallback: (Long) -> Unit
private val fragment: ChildFragment<*>,
emptyView: View?,
private val viewModel: MainViewModel
) : BaseListAdapter<ShiftObject>(diffCallBack, R.layout.list_item_1, emptyView) {
@SuppressLint("SetTextI18n")
@@ -33,63 +35,91 @@ class ShiftListAdapter(
val descriptionTextView: TextView = view.findViewById(R.id.location)
val dateTextView: TextView = view.findViewById(R.id.date)
val totalPay: TextView = view.findViewById(R.id.total_pay)
val hoursView: TextView = view.findViewById(R.id.hours)
val h: TextView = view.findViewById(R.id.h)
val minutesView: TextView = view.findViewById(R.id.minutes)
val m: TextView = view.findViewById(R.id.m)
val editView: ImageView = view.findViewById(R.id.imageView)
h.text = "h"
m.text = "m"
val typeText: String = data.type
val descriptionText: String = data.description
val dateText: String = data.date
val totalPayText: String = data.totalPay.formatToTwoDpString()
descriptionTextView.text = descriptionText
dateTextView.text = dateText
totalPay.text = totalPayText
when (getItemViewType(position)) {
HOURLY_ITEM -> {
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()
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()
hoursView.text = unitsText
h.text = ""
minutesView.text = ""
m.text = "pcs"
unitsView.text = unitsText
}
}
descriptionTextView.text = data.description
dateTextView.text = data.date
totalPay.text = data.totalPay.formatToTwoDpString()
view.setOnClickListener {
// Navigate to further info
val nav = FragmentMainDirections.mainToFurtherInfo(data.id)
fragment.navigateTo(nav)
fragment.navigateParent(nav)
}
editView.setOnClickListener {
// Navigate to edit
val nav = FragmentMainDirections.mainToAddItem(data.id)
fragment.navigateTo(nav)
}
view.setOnLongClickListener {
AlertDialog.Builder(it.context)
.setMessage("Are you sure you want to delete")
.setPositiveButton("delete") { _, _ -> longPressCallback.invoke(data.id) }
.setNegativeButton("cancel") { dialog, _ ->
dialog?.dismiss()
//creating a popup menu
val popup = PopupMenu(it.context, it)
//inflating menu from xml resource
popup.inflate(R.menu.options_menu)
//adding click listener
popup.setOnMenuItemClickListener { menu ->
when (menu.itemId) {
R.id.update -> {
// 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 {
val diffCallBack = object : DiffUtil.ItemCallback<ShiftObject>() {
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)
}
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
*

View File

@@ -20,7 +20,7 @@ fun <CLASS : Any> Any.getGenericClassAt(position: Int): KClass<CLASS> =
* var s: String?
* 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)) {
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
* 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) {
Order.ASCENDING -> sortedWith(compareBy(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.Navigation
import com.appttude.h_mal.farmr.R
import com.appttude.h_mal.farmr.base.ChildFragment
fun Fragment.navigateToFragment(newFragment: Fragment) {
childFragmentManager.beginTransaction()

View File

@@ -1,8 +1,12 @@
package com.appttude.h_mal.farmr.viewmodel
import android.graphics.Color
import android.graphics.drawable.Drawable
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
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.legacydb.ShiftObject
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.Success
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.sortedByOrder
import jxl.Workbook
@@ -30,6 +35,7 @@ import jxl.write.WritableWorkbook
import jxl.write.WriteException
import java.io.File
import java.io.IOException
import java.util.Calendar
import java.util.Locale
@@ -43,6 +49,8 @@ class MainViewModel(
private var mSort: Sortable = Sortable.ID
private var mOrder: Order = Order.ASCENDING
private var selectedItemId: Int? = null
private val observer = Observer<List<ShiftObject>> {
it?.let {
val result = it.applyFilters().sortList(mSort, mOrder)
@@ -93,7 +101,7 @@ class MainViewModel(
if (second == null) return compareDate.after(first)
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() {
super.setFiltrationDetails(null, null, null, null)
onSuccess(Success("Filters have been cleared"))
refreshLiveData()
val result = super.setFiltrationDetails(null, null, null, null)
if (result) refreshLiveData()
}
fun createExcelSheet(file: File): File? {
@@ -283,4 +290,25 @@ class MainViewModel(
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)
/**
* 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(
description: String?,
dateFrom: String?,
dateTo: String?,
type: String?
) {
repository.setFilteringDetailsInPrefs(description, dateFrom, dateTo, type)
): Boolean {
return repository.setFilteringDetailsInPrefs(description, dateFrom, dateTo, type)
}
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"
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.FragmentMain">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_item_view"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/sub_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/list_item_1">
</androidx.recyclerview.widget.RecyclerView>
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottom_bar"
app:layout_constraintLeft_toLeftOf="parent"
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
android:id="@+id/fab1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/bottom_bar"
android:layout_margin="@dimen/fab_margin"
android:contentDescription="@string/fab"
android:src="@drawable/add"
app:backgroundTint="@color/colorPrimary" />
<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>
</androidx.constraintlayout.widget.ConstraintLayout>

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"?>
<LinearLayout 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"
@@ -8,10 +9,11 @@
android:orientation="horizontal"
android:paddingBottom="24dp"
android:paddingTop="24dp"
android:showDividers="end">
android:showDividers="end"
tools:ignore="HardcodedText">
<LinearLayout
android:layout_width="87dp"
android:layout_width="@dimen/unit_holder_width"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
@@ -30,7 +32,7 @@
android:layout_height="wrap_content"
android:text="00"
android:textColor="#143d66"
android:textSize="30sp" />
android:textSize="@dimen/unit_text_size" />
<TextView
android:id="@+id/h"
@@ -38,7 +40,7 @@
android:layout_height="wrap_content"
android:text="h"
android:textColor="#143d66"
android:textSize="12sp" />
android:textSize="@dimen/units_symbol_size" />
<TextView
android:id="@+id/minutes"
@@ -47,7 +49,7 @@
android:layout_weight="1"
android:text="00"
android:textColor="#143d66"
android:textSize="30sp" />
android:textSize="@dimen/unit_text_size"/>
<TextView
android:id="@+id/m"
@@ -56,7 +58,7 @@
android:layout_weight="1"
android:text="m"
android:textColor="#143d66"
android:textSize="12sp" />
android:textSize="@dimen/units_symbol_size" />
</LinearLayout>
<LinearLayout
@@ -70,9 +72,9 @@
android:id="@+id/currency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="£"
android:text="@string/pound_sign"
android:textColor="#728fcc"
android:textSize="12sp" />
android:textSize="@dimen/units_symbol_size" />
<TextView
android:id="@+id/total_pay"
@@ -80,7 +82,7 @@
android:layout_height="wrap_content"
android:text="000.00"
android:textColor="#728fcc"
android:textSize="20sp" />
android:textSize="@dimen/total_pay_size" />
</LinearLayout>
</LinearLayout>
@@ -106,7 +108,7 @@
android:layout_below="@+id/date"
android:maxLines="3"
android:text="Location Name"
android:textSize="20sp" />
android:textSize="@dimen/location_size" />
<TextView
android:id="@+id/date"
@@ -119,7 +121,7 @@
android:layout_marginTop="16dp"
android:text="01-05-2010"
android:textColor="#000000"
android:textSize="16sp" />
android:textSize="@dimen/date_size" />
<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" >
<action
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
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
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
android:id="@+id/fragmentAddItem"
@@ -41,7 +53,11 @@
tools:layout="@layout/fragment_futher_info" >
<action
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
android:name="shiftId"
app:argType="long" />

View File

@@ -4,6 +4,13 @@
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</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>

View File

@@ -87,14 +87,18 @@
<string name="export">Export Data</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>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="further_info_title">Shift Details</string>
<string name="insert_break_in_minutes">insert break in minutes</string>
<string name="break_res">Break</string>
<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>

View File

@@ -90,7 +90,7 @@ class MainViewModelTest {
val retrievedShifts = retrieveCurrentData()
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())
viewModel.clearFilters()
val descriptionAfterClearedFilter = viewModel.getInformation()