From 521faf058d11fc282f7645cc7aea1bf7ada20b1b Mon Sep 17 00:00:00 2001 From: NicksPatties Date: Tue, 9 Aug 2022 13:01:31 -0700 Subject: [PATCH 1/4] dismiss battery warning moved to ClockPageViewModelState --- .../nickspatties/timeclock/ui/pages/ClockPage.kt | 12 +++++++++++- .../timeclock/ui/viewmodel/ClockPageViewModel.kt | 14 +++++++------- .../ui/viewmodel/ClockPageViewModelTest.kt | 9 +++++++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/nickspatties/timeclock/ui/pages/ClockPage.kt b/app/src/main/java/com/nickspatties/timeclock/ui/pages/ClockPage.kt index 4578c63..2b0ec7f 100644 --- a/app/src/main/java/com/nickspatties/timeclock/ui/pages/ClockPage.kt +++ b/app/src/main/java/com/nickspatties/timeclock/ui/pages/ClockPage.kt @@ -30,7 +30,7 @@ fun ClockPage( if (viewModelState.batteryWarningDialogVisible) { BatteryWarningDialog( confirmFunction = viewModelState.batteryWarningConfirmFunction, - dismissFunction = viewModelState.batteryWarningDismissFunction + dismissFunction = viewModelState::dismissBatteryWarningDialog ) } @@ -138,4 +138,14 @@ fun ClockPage_Initial() { ClockPage( viewModelState = ClockPageViewModelState() ) +} + +@Composable +@Preview +fun ClockPage_batteryDialogVisible() { + ClockPage( + viewModelState = ClockPageViewModelState( + batteryWarningDialogVisible = true + ) + ) } \ No newline at end of file diff --git a/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt b/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt index 9629a5a..e25f387 100644 --- a/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt +++ b/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt @@ -84,7 +84,6 @@ class ClockPageViewModel ( init { // state function declarations state.batteryWarningConfirmFunction = this::goToBatterySettings - state.batteryWarningDismissFunction = this::hideBatteryWarningModal state.onTaskNameIconClick = this::switchCountDownTimer state.onTimerAnimationFinish = this::resetCurrSeconds state.onClockStart = this::startClock @@ -158,9 +157,7 @@ class ClockPageViewModel ( } } - fun hideBatteryWarningModal() { - state.batteryWarningDialogVisible = false - } + /** * Allow user to give TimeClock permission to run in the background unrestricted, which @@ -177,7 +174,7 @@ class ClockPageViewModel ( Uri.parse("package:" + getApplication().packageName) startActivity(getApplication(), intentBatteryUsage, null) } - hideBatteryWarningModal() + state.dismissBatteryWarningDialog() } private fun timerTextFieldValuesToSeconds(): Int { @@ -330,7 +327,7 @@ fun getCountDownSeconds( * @param secondsTextFieldValue The text in the minutes section of the EditTimerTextField. Should * not exceed 59 * @param batteryWarningConfirmFunction Fires when tapping confirm button in BatteryWarningDialog - * @param batteryWarningDismissFunction Fires when tapping outside the BatteryWarningDialog + * @param dismissBatteryWarningDialog Fires when tapping outside the BatteryWarningDialog * @param onTaskNameIconClick Fires when the icon in the TaskTextField is pushed. Changes from * count up to count down mode. * @param onDismissDropdown Fires when the dropdown in the TaskTextField is dismissed by tapping outside it @@ -359,7 +356,6 @@ class ClockPageViewModelState( selection = TextRange(0) ), var batteryWarningConfirmFunction: () -> Unit = {}, - var batteryWarningDismissFunction: () -> Unit = {}, var onTaskNameIconClick: () -> Unit = {}, var onDismissDropdown: () -> Unit = {}, var onTimerAnimationFinish: () -> Unit = {}, @@ -398,6 +394,10 @@ class ClockPageViewModelState( // cheeky var used to prevent onTaskNameChange from being called after onDropdownMenuItemClick var dropdownClicked = false + fun dismissBatteryWarningDialog() { + batteryWarningDialogVisible = false + } + fun onTaskNameChange(tfv: TextFieldValue) { if (dropdownClicked) { dropdownClicked = false diff --git a/app/src/test/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModelTest.kt b/app/src/test/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModelTest.kt index 28edc0f..83f2999 100644 --- a/app/src/test/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModelTest.kt +++ b/app/src/test/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModelTest.kt @@ -243,4 +243,13 @@ class ClockPageViewModelStateTest { ) ) } + + @Test + fun dismissBatteryWarningDialog_dialogNotVisibleAfterDismiss() { + val testState = ClockPageViewModelState( + batteryWarningDialogVisible = true + ) + testState.dismissBatteryWarningDialog() + assertThat(testState.batteryWarningDialogVisible).isFalse() + } } \ No newline at end of file From 8669c15d64a9baff210f07a8c2f6716c944840ec Mon Sep 17 00:00:00 2001 From: NicksPatties Date: Tue, 9 Aug 2022 14:15:49 -0700 Subject: [PATCH 2/4] Add startBatteryManagementActivity to ClockPageViewModelState. Completed separation of batteryWarningDialog state logic from viewmodel. --- .../nickspatties/timeclock/ui/pages/ClockPage.kt | 2 +- .../timeclock/ui/viewmodel/ClockPageViewModel.kt | 13 ++++++++----- .../ui/viewmodel/ClockPageViewModelTest.kt | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/nickspatties/timeclock/ui/pages/ClockPage.kt b/app/src/main/java/com/nickspatties/timeclock/ui/pages/ClockPage.kt index 2b0ec7f..e9e17b8 100644 --- a/app/src/main/java/com/nickspatties/timeclock/ui/pages/ClockPage.kt +++ b/app/src/main/java/com/nickspatties/timeclock/ui/pages/ClockPage.kt @@ -29,7 +29,7 @@ fun ClockPage( if (viewModelState.batteryWarningDialogVisible) { BatteryWarningDialog( - confirmFunction = viewModelState.batteryWarningConfirmFunction, + confirmFunction = viewModelState::confirmBatteryWarningDialog, dismissFunction = viewModelState::dismissBatteryWarningDialog ) } diff --git a/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt b/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt index e25f387..f81c2dc 100644 --- a/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt +++ b/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt @@ -83,7 +83,7 @@ class ClockPageViewModel ( // only occurs when the class is created, not when moving from view to view init { // state function declarations - state.batteryWarningConfirmFunction = this::goToBatterySettings + state.startBatteryManagementActivity = this::goToBatterySettings state.onTaskNameIconClick = this::switchCountDownTimer state.onTimerAnimationFinish = this::resetCurrSeconds state.onClockStart = this::startClock @@ -174,7 +174,6 @@ class ClockPageViewModel ( Uri.parse("package:" + getApplication().packageName) startActivity(getApplication(), intentBatteryUsage, null) } - state.dismissBatteryWarningDialog() } private fun timerTextFieldValuesToSeconds(): Int { @@ -326,8 +325,7 @@ fun getCountDownSeconds( * not exceed 59 * @param secondsTextFieldValue The text in the minutes section of the EditTimerTextField. Should * not exceed 59 - * @param batteryWarningConfirmFunction Fires when tapping confirm button in BatteryWarningDialog - * @param dismissBatteryWarningDialog Fires when tapping outside the BatteryWarningDialog + * @param startBatteryManagementActivity Starts battery management activity * @param onTaskNameIconClick Fires when the icon in the TaskTextField is pushed. Changes from * count up to count down mode. * @param onDismissDropdown Fires when the dropdown in the TaskTextField is dismissed by tapping outside it @@ -355,7 +353,7 @@ class ClockPageViewModelState( text = "00", selection = TextRange(0) ), - var batteryWarningConfirmFunction: () -> Unit = {}, + var startBatteryManagementActivity: () -> Unit = {}, var onTaskNameIconClick: () -> Unit = {}, var onDismissDropdown: () -> Unit = {}, var onTimerAnimationFinish: () -> Unit = {}, @@ -398,6 +396,11 @@ class ClockPageViewModelState( batteryWarningDialogVisible = false } + fun confirmBatteryWarningDialog() { + startBatteryManagementActivity() + batteryWarningDialogVisible = false + } + fun onTaskNameChange(tfv: TextFieldValue) { if (dropdownClicked) { dropdownClicked = false diff --git a/app/src/test/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModelTest.kt b/app/src/test/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModelTest.kt index 83f2999..ce3973d 100644 --- a/app/src/test/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModelTest.kt +++ b/app/src/test/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModelTest.kt @@ -244,6 +244,20 @@ class ClockPageViewModelStateTest { ) } + @Test + fun confirmBatteryWarningDialog_callsStartBatteryManagementActivityFunction() { + var counter = 0 + val testState = ClockPageViewModelState( + batteryWarningDialogVisible = true, + startBatteryManagementActivity = { + counter++ + } + ) + testState.confirmBatteryWarningDialog() + assertThat(counter).isEqualTo(1) + assertThat(testState.batteryWarningDialogVisible).isFalse() + } + @Test fun dismissBatteryWarningDialog_dialogNotVisibleAfterDismiss() { val testState = ClockPageViewModelState( From 6e32324f1f08a56a175220aab76a790bc00ebafe Mon Sep 17 00:00:00 2001 From: NicksPatties Date: Fri, 12 Aug 2022 11:54:57 -0600 Subject: [PATCH 3/4] Finish moving TaskTextFieldIcon functionality to ViewModelState, kept data saving logic in ViewModel. Wrote unit and instrumented tests. --- .../timeclock/ui/pages/ClockPageTest.kt | 40 ++++++++++++++++- .../timeclock/ui/pages/ClockPage.kt | 4 +- .../ui/viewmodel/ClockPageViewModel.kt | 45 +++++++++++-------- .../ui/viewmodel/ClockPageViewModelTest.kt | 25 ++++++++++- 4 files changed, 91 insertions(+), 23 deletions(-) diff --git a/app/src/androidTest/java/com/nickspatties/timeclock/ui/pages/ClockPageTest.kt b/app/src/androidTest/java/com/nickspatties/timeclock/ui/pages/ClockPageTest.kt index 5094aef..007d43b 100644 --- a/app/src/androidTest/java/com/nickspatties/timeclock/ui/pages/ClockPageTest.kt +++ b/app/src/androidTest/java/com/nickspatties/timeclock/ui/pages/ClockPageTest.kt @@ -46,6 +46,44 @@ class ClockPageTest { .assertTextEquals(testString) } + @Test + fun taskNameIcon_countDownTimerAppears() { + composeTestRule.setContent { + TimeClockTheme { + ClockPage( + viewModelState = ClockPageViewModelState() + ) + } + } + composeTestRule.onNodeWithTag("TaskTextField_IconButton").performClick() + composeTestRule.onNodeWithTag("TimerTextField_Hours") + .assertIsDisplayed() + .assertIsEnabled() + composeTestRule.onNodeWithTag("TimerTextField_Minutes") + .assertIsDisplayed() + .assertIsEnabled() + composeTestRule.onNodeWithTag("TimerTextField_Seconds") + .assertIsDisplayed() + .assertIsEnabled() + } + + @Test + fun taskNameIcon_countDownTimerDisappearsIfAlreadyEnabled() { + composeTestRule.setContent { + TimeClockTheme { + ClockPage( + viewModelState = ClockPageViewModelState( + countDownTimerEnabled = true + ) + ) + } + } + composeTestRule.onNodeWithTag("TaskTextField_IconButton").performClick() + composeTestRule.onNodeWithTag("TimerTextField_Hours").assertDoesNotExist() + composeTestRule.onNodeWithTag("TimerTextField_Minutes").assertDoesNotExist() + composeTestRule.onNodeWithTag("TimerTextField_Seconds").assertDoesNotExist() + } + @Test fun taskNameDropdown_dropdownAppearsAndTaskFillsInWhenLabelIsClicked() { composeTestRule.setContent { @@ -123,4 +161,4 @@ class ClockPageTest { composeTestRule.onNodeWithTag("TimerTextField_Minutes").performTextInput("1") composeTestRule.onNodeWithTag("StartTimerButton").assertIsEnabled() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/nickspatties/timeclock/ui/pages/ClockPage.kt b/app/src/main/java/com/nickspatties/timeclock/ui/pages/ClockPage.kt index e9e17b8..37b0509 100644 --- a/app/src/main/java/com/nickspatties/timeclock/ui/pages/ClockPage.kt +++ b/app/src/main/java/com/nickspatties/timeclock/ui/pages/ClockPage.kt @@ -34,7 +34,7 @@ fun ClockPage( ) } - Scaffold() { + Scaffold { Column( modifier = Modifier .padding(it) @@ -63,7 +63,7 @@ fun ClockPage( }, keyboardController = keyboardController, countdownTimerEnabled = viewModelState.countDownTimerEnabled, - onIconClick = viewModelState.onTaskNameIconClick + onIconClick = viewModelState::onTaskTextFieldIconClick ) DropdownMenu( diff --git a/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt b/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt index f81c2dc..9243331 100644 --- a/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt +++ b/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt @@ -83,8 +83,9 @@ class ClockPageViewModel ( // only occurs when the class is created, not when moving from view to view init { // state function declarations + state.checkBatteryOptimizationSettings = this::checkBatteryOptimizationSettings state.startBatteryManagementActivity = this::goToBatterySettings - state.onTaskNameIconClick = this::switchCountDownTimer + state.saveCountDownTimerEnabledValue = this::saveCountDownTimerEnabled state.onTimerAnimationFinish = this::resetCurrSeconds state.onClockStart = this::startClock state.onClockStop = this::stopClock @@ -138,27 +139,22 @@ class ClockPageViewModel ( } } - fun switchCountDownTimer() { - val shouldWarn = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + private fun checkBatteryOptimizationSettings() : Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // can just check if the permission is set !powerManager.isIgnoringBatteryOptimizations(getApplication().packageName) } else { // is unable to change the battery permission for versions below M, so only false false } + } - if (!state.countDownTimerEnabled && shouldWarn) { - state.batteryWarningDialogVisible = true - } else { - state.countDownTimerEnabled = !state.countDownTimerEnabled - viewModelScope.launch { - userPreferencesRepository.updateCountDownEnabled(state.countDownTimerEnabled) - } + private fun saveCountDownTimerEnabled(enabled: Boolean) { + viewModelScope.launch { + userPreferencesRepository.updateCountDownEnabled(enabled) } } - - /** * Allow user to give TimeClock permission to run in the background unrestricted, which * is necessary for countdowns and exact alarms to run correctly. Pre-Marshmallow devices @@ -318,18 +314,18 @@ fun getCountDownSeconds( * @param batteryWarningDialogVisible Determines if the battery warning dialog is visible * @param countDownTimerEnabled Determines if the count down timer should be visible, allowing * the user to set a time that an event will last. - * @param countDownEndTime Not sure if it'll be here in the future... - * @param currCountDownSeconds Not sure if this'll be here either... * @param hoursTextFieldValue The text in the hours section of the EditTimerTextField * @param minutesTextFieldValue The text in the minutes section of the EditTimerTextField. Should * not exceed 59 * @param secondsTextFieldValue The text in the minutes section of the EditTimerTextField. Should * not exceed 59 + * @param checkBatteryOptimizationSettings Verifies whether or not the TimeClock has unrestricted + * battery permission. If it doesn't, then the battery warning dialog should appear. * @param startBatteryManagementActivity Starts battery management activity - * @param onTaskNameIconClick Fires when the icon in the TaskTextField is pushed. Changes from - * count up to count down mode. - * @param onDismissDropdown Fires when the dropdown in the TaskTextField is dismissed by tapping outside it - * @param onTimerAnimationFinish Fires when the count up timer fades in and out when starting recording + * @param onDismissDropdown Fires when the dropdown in the TaskTextField is dismissed by + * tapping outside it + * @param onTimerAnimationFinish Fires when the count up timer fades in and out when starting + * recording * @param onClockStart Fires when the start button is pressed * @param onClockStop Fires when the stop button is pressed */ @@ -353,8 +349,9 @@ class ClockPageViewModelState( text = "00", selection = TextRange(0) ), + var checkBatteryOptimizationSettings: () -> Boolean = {false}, var startBatteryManagementActivity: () -> Unit = {}, - var onTaskNameIconClick: () -> Unit = {}, + var saveCountDownTimerEnabledValue: (Boolean) -> Unit = {_ -> }, var onDismissDropdown: () -> Unit = {}, var onTimerAnimationFinish: () -> Unit = {}, var onClockStart: () -> Unit = {}, @@ -401,6 +398,16 @@ class ClockPageViewModelState( batteryWarningDialogVisible = false } + fun onTaskTextFieldIconClick() { + val shouldWarn = checkBatteryOptimizationSettings() + if (!countDownTimerEnabled && shouldWarn) { + batteryWarningDialogVisible = true + } else { + countDownTimerEnabled = !countDownTimerEnabled + saveCountDownTimerEnabledValue(countDownTimerEnabled) + } + } + fun onTaskNameChange(tfv: TextFieldValue) { if (dropdownClicked) { dropdownClicked = false diff --git a/app/src/test/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModelTest.kt b/app/src/test/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModelTest.kt index ce3973d..35650b9 100644 --- a/app/src/test/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModelTest.kt +++ b/app/src/test/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModelTest.kt @@ -266,4 +266,27 @@ class ClockPageViewModelStateTest { testState.dismissBatteryWarningDialog() assertThat(testState.batteryWarningDialogVisible).isFalse() } -} \ No newline at end of file + + @Test + fun onTaskTextFieldIconClick_switchesCountDownTimerEnabled() { + var counter = 0 + val testState = ClockPageViewModelState( + countDownTimerEnabled = false, + saveCountDownTimerEnabledValue = { counter++ } + ) + testState.onTaskTextFieldIconClick() + assertThat(testState.countDownTimerEnabled).isTrue() + assertThat(counter).isEqualTo(1) // saveCountDownTimerEnabled has been called + } + + @Test + fun onTaskTextFieldIconClick_shouldWarnIfBatterySettingsNotOptimized() { + val testState = ClockPageViewModelState( + checkBatteryOptimizationSettings = { true }, + countDownTimerEnabled = false + ) + testState.onTaskTextFieldIconClick() + assertThat(testState.countDownTimerEnabled).isFalse() + assertThat(testState.batteryWarningDialogVisible).isTrue() + } +} From 344eebae441cf9712092621533963555fb55daef Mon Sep 17 00:00:00 2001 From: NicksPatties Date: Fri, 12 Aug 2022 12:16:04 -0600 Subject: [PATCH 4/4] Add javadoc comment to saveCountDownTimerEnabledValue --- .../nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt b/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt index 9243331..2b6f2dd 100644 --- a/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt +++ b/app/src/main/java/com/nickspatties/timeclock/ui/viewmodel/ClockPageViewModel.kt @@ -322,6 +322,8 @@ fun getCountDownSeconds( * @param checkBatteryOptimizationSettings Verifies whether or not the TimeClock has unrestricted * battery permission. If it doesn't, then the battery warning dialog should appear. * @param startBatteryManagementActivity Starts battery management activity + * @param saveCountDownTimerEnabledValue Saves the countDownTimerEnabled UserPreference variable, so + * the count down timer's visibility persists on activity recreation * @param onDismissDropdown Fires when the dropdown in the TaskTextField is dismissed by * tapping outside it * @param onTimerAnimationFinish Fires when the count up timer fades in and out when starting