Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

19 feature notify users to change battery permission settings #22

Merged
6 changes: 5 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<!--
Remove this permission below if the Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
activity is no longer being started in ClockPageViewModel::goToBatterySettings
-->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

<application
android:allowBackup="true"
Expand All @@ -15,7 +20,6 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant label removed

android:theme="@style/Theme.TimeClock.NoActionBar"
android:screenOrientation="portrait"
android:launchMode="singleTask">
Expand Down
40 changes: 1 addition & 39 deletions app/src/main/java/com/nickspatties/timeclock/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -139,26 +139,6 @@ fun NavigationComponent(
startDestination: String
) {
val clockPageViewModel = viewModel.clockPage
val clockEnabled = clockPageViewModel.clockButtonEnabled
val isRunning = clockPageViewModel.isClockRunning
val dropdownExpanded = clockPageViewModel.dropdownExpanded
// observe changes on autofillTaskNames to allow filteredTaskNames to function properly
clockPageViewModel.autofillTaskNames.observeAsState()
val filteredTaskNames = clockPageViewModel.filteredEventNames
val taskTextFieldValue = clockPageViewModel.taskTextFieldValue
val currSeconds = clockPageViewModel.currSeconds
val onTaskNameChange = clockPageViewModel::onTaskNameChange
val onTaskNameDonePressed = clockPageViewModel::onTaskNameDonePressed
val onDismissDropdown = clockPageViewModel::onDismissDropdown
val onDropdownMenuItemClick = clockPageViewModel::onDropdownMenuItemClick
val startClock = clockPageViewModel::startClock
val stopClock = clockPageViewModel::stopClock
val onTimerAnimationFinished = clockPageViewModel::resetCurrSeconds
val countdownEnabled = clockPageViewModel.countDownTimerEnabled
val onCountdownIconClicked = clockPageViewModel::switchCountDownTimer
val hoursTextFieldValue = clockPageViewModel.hoursTextFieldValue
val minutesTextFieldValue = clockPageViewModel.minutesTextFieldValue
val secondsTextFieldValue = clockPageViewModel.secondsTextFieldValue
Comment on lines -142 to -161
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Want to only pass the ViewModel into components now, instead of a giant pile of parameters. However, this takes away the ability to view the ClockPage's compose mockup, which is not a big deal since I don't see it changing soon.

I am going to play around with different ways to pass state parameters and functions into these higher order components, to see how things will be tested the easiest.


val listPageViewModel = viewModel.listPage
val groupedEvents = listPageViewModel.groupedEventsByDate.observeAsState().value
Expand Down Expand Up @@ -190,25 +170,7 @@ fun NavigationComponent(
) {
composable(clockRoute) {
ClockPage(
viewModel = clockPageViewModel,
clockEnabled = clockEnabled,
isRunning = isRunning,
dropdownExpanded = dropdownExpanded,
filteredTaskNames = filteredTaskNames,
taskTextFieldValue = taskTextFieldValue,
currSeconds = currSeconds,
onTaskNameChange = onTaskNameChange,
onTaskNameDonePressed = onTaskNameDonePressed,
onDismissDropdown = onDismissDropdown,
onDropdownMenuItemClick = onDropdownMenuItemClick,
startClock = startClock,
stopClock = stopClock,
timerAnimationFinishedListener = onTimerAnimationFinished,
onCountdownIconClicked = onCountdownIconClicked,
countdownEnabled = countdownEnabled,
hoursTextFieldValue = hoursTextFieldValue,
minutesTextFieldValue = minutesTextFieldValue,
secondsTextFieldValue = secondsTextFieldValue
viewModel = clockPageViewModel
)
}
composable(listRoute) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ data class UserPreferences(
val countDownEndTime: Long
)

class UserPreferencesRepository(private val dataStore: DataStore<Preferences>) {
object PreferenceKeys {
val COUNT_DOWN_END_TIME = longPreferencesKey("count_down_end_time")
val COUNT_DOWN_ENABLED = booleanPreferencesKey("count_down_enabled")
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can move this back into the class for now if I want. For simplicity of testing, I wanted to see if I can make updating these preferences generic, which meant I moved these keys out of the class so I can reference them elsewhere.


// list of keys that are used to maintain state in the app
private object PreferenceKeys {
val COUNT_DOWN_END_TIME = longPreferencesKey("count_down_end_time")
val COUNT_DOWN_ENABLED = booleanPreferencesKey("count_down_enabled")
}
class UserPreferencesRepository(private val dataStore: DataStore<Preferences>) {

val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
.catch { exception ->
Expand All @@ -43,15 +42,15 @@ class UserPreferencesRepository(private val dataStore: DataStore<Preferences>) {
return UserPreferences(countDownEnabled, countDownEndTime)
}

suspend fun updateCountDownEnabled(enabled: Boolean) {
suspend fun updateCountDownEndTime(endTime: Long) {
dataStore.edit {
it[PreferenceKeys.COUNT_DOWN_ENABLED] = enabled
it[PreferenceKeys.COUNT_DOWN_END_TIME] = endTime
}
}

suspend fun updateCountDownEndTime(endTime: Long) {
suspend fun updateCountDownEnabled(enabled: Boolean) {
dataStore.edit {
it[PreferenceKeys.COUNT_DOWN_END_TIME] = endTime
it[PreferenceKeys.COUNT_DOWN_ENABLED] = enabled
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.nickspatties.timeclock.ui.components

import androidx.compose.material.AlertDialog
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.nickspatties.timeclock.R

@Composable
@Preview
fun BatteryWarningDialog(
confirmFunction: () -> Unit = {},
dismissFunction: () -> Unit = {}
) {
AlertDialog(
modifier = Modifier,
onDismissRequest = dismissFunction,
title = { Text(stringResource(id = R.string.battery_warning_dialog_title)) },
text = { Text(stringResource(id = R.string.battery_warning_dialog_body)) },
confirmButton = {
TextButton(onClick = confirmFunction) {
Text(text = stringResource(id = R.string.battery_warning_dialog_action).uppercase())
}
},
dismissButton = null
)
}
115 changes: 43 additions & 72 deletions app/src/main/java/com/nickspatties/timeclock/ui/pages/ClockPage.kt
Original file line number Diff line number Diff line change
@@ -1,53 +1,39 @@
package com.nickspatties.timeclock.ui.pages

import androidx.compose.foundation.layout.*
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.PopupProperties
import com.nickspatties.timeclock.ui.components.EditTimerTextField
import com.nickspatties.timeclock.ui.components.StartTimerButton
import com.nickspatties.timeclock.ui.components.TaskTextField
import com.nickspatties.timeclock.ui.components.TimerText
import com.nickspatties.timeclock.ui.components.*
import com.nickspatties.timeclock.ui.viewmodel.ClockPageViewModel

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun ClockPage(
viewModel: ClockPageViewModel?,
clockEnabled: Boolean,
isRunning: Boolean,
countdownEnabled: Boolean = false,
dropdownExpanded: Boolean,
taskTextFieldValue: TextFieldValue,
filteredTaskNames: List<String> = listOf(),
currSeconds: Int,
onTaskNameChange: (TextFieldValue) -> Unit,
onTaskNameDonePressed: () -> Unit,
onDismissDropdown: () -> Unit,
onDropdownMenuItemClick: (String) -> Unit,
startClock: () -> Unit,
stopClock: (Boolean) -> Unit,
timerAnimationFinishedListener: () -> Unit = {},
onCountdownIconClicked: () -> Unit,
hoursTextFieldValue: TextFieldValue,
minutesTextFieldValue: TextFieldValue,
secondsTextFieldValue: TextFieldValue
viewModel: ClockPageViewModel
) {
val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current

// observe changes on autofillTaskNames to allow filteredTaskNames to function properly
viewModel.autofillTaskNames.observeAsState()

if (viewModel.batteryWarningDialogVisible) {
BatteryWarningDialog(
confirmFunction = { viewModel.goToBatterySettings() },
dismissFunction = { viewModel.hideBatteryWarningModal() }
)
}

Scaffold() {
Column(
modifier = Modifier.fillMaxSize(),
Expand All @@ -61,44 +47,44 @@ fun ClockPage(
TaskTextField(
modifier = Modifier
.fillMaxWidth(widthFraction),
value = taskTextFieldValue,
enabled = !isRunning,
value = viewModel.taskTextFieldValue,
enabled = !viewModel.isClockRunning,
onTaskNameChange = {
onTaskNameChange(it)
(viewModel::onTaskNameChange)(it)
},
onDone = {
if (countdownEnabled) {
if (viewModel.countDownTimerEnabled) {
focusManager.moveFocus(FocusDirection.Next)
} else {
focusManager.clearFocus()
}
onTaskNameDonePressed()
(viewModel::onTaskNameDonePressed)()
},
keyboardController = keyboardController,
countdownTimerEnabled = countdownEnabled,
onIconClick = onCountdownIconClicked
countdownTimerEnabled = viewModel.countDownTimerEnabled,
onIconClick = viewModel::switchCountDownTimer
)

DropdownMenu(
modifier = Modifier
// 144 = 3 * 48 (the default height of a DropdownMenuItem
.requiredSizeIn(maxHeight = 144.dp)
.fillMaxWidth(widthFraction),
expanded = dropdownExpanded,
expanded = viewModel.dropdownExpanded,
properties = PopupProperties(focusable = false),
onDismissRequest = {
onDismissDropdown()
(viewModel::onDismissDropdown)()
},
) {
filteredTaskNames.forEach { label ->
viewModel.filteredEventNames.forEach { label ->
DropdownMenuItem(onClick = {
if (countdownEnabled) {
if (viewModel.countDownTimerEnabled) {
focusManager.moveFocus(FocusDirection.Next)
} else {
focusManager.clearFocus()
keyboardController?.hide()
}
onDropdownMenuItemClick(label)
(viewModel::onDropdownMenuItemClick)(label)
}) {
Text(text = label)
}
Expand All @@ -108,13 +94,13 @@ fun ClockPage(

// timer clock
val spacing = 0.dp
if (countdownEnabled) {
if (viewModel.countDownTimerEnabled) {
EditTimerTextField(
viewModel = viewModel!!,
hoursTextFieldValue = hoursTextFieldValue,
minutesTextFieldValue = minutesTextFieldValue,
secondsTextFieldValue = secondsTextFieldValue,
clickable = !isRunning,
viewModel = viewModel,
hoursTextFieldValue = viewModel.hoursTextFieldValue,
minutesTextFieldValue = viewModel.minutesTextFieldValue,
secondsTextFieldValue = viewModel.secondsTextFieldValue,
clickable = !viewModel.isClockRunning,
focusManager = focusManager
)
} else {
Expand All @@ -123,17 +109,17 @@ fun ClockPage(
top = spacing,
bottom = spacing
),
isRunning = isRunning,
currSeconds = currSeconds,
finishedListener = timerAnimationFinishedListener
isRunning = viewModel.isClockRunning,
currSeconds = viewModel.currSeconds,
finishedListener = viewModel::resetCurrSeconds
)
}

StartTimerButton(
clockEnabled = clockEnabled,
isRunning = isRunning,
startClock = startClock,
stopClock = stopClock
clockEnabled = viewModel.clockButtonEnabled,
isRunning = viewModel.isClockRunning,
startClock = viewModel::startClock,
stopClock = viewModel::stopClock
)
}
}
Expand All @@ -142,23 +128,8 @@ fun ClockPage(
@Composable
@Preview
fun ClockPageMockUp() {
val defaultTextFieldValue = TextFieldValue("00")
ClockPage(
clockEnabled = false,
isRunning = false,
dropdownExpanded = false,
taskTextFieldValue = TextFieldValue(),
currSeconds = 0,
onTaskNameChange = { },
onTaskNameDonePressed = { },
onDismissDropdown = { },
onDropdownMenuItemClick = { },
startClock = { },
stopClock = { },
onCountdownIconClicked = {},
hoursTextFieldValue = defaultTextFieldValue,
minutesTextFieldValue = defaultTextFieldValue,
secondsTextFieldValue = defaultTextFieldValue,
viewModel = null
)
// val defaultTextFieldValue = TextFieldValue("00")
// ClockPage(
// viewModel = null
// )
}
Loading