From 9ec3e6851f246760ef62e22a907886aec2d41e2f Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Sun, 9 Nov 2025 22:08:47 +0530 Subject: [PATCH] feat(settings): disable editing time when service is running, auto reload when navigating to timer screen --- .../nsh07/pomodoro/data/TimerRepository.kt | 5 ++- .../nsh07/pomodoro/service/TimerService.kt | 4 +- .../ui/settingsScreen/SettingsScreen.kt | 4 ++ .../ui/settingsScreen/SettingsSwitchItem.kt | 1 + .../components/MinuteInputField.kt | 4 +- .../settingsScreen/screens/TimerSettings.kt | 39 +++++++++++++++++-- .../viewModel/SettingsViewModel.kt | 5 ++- .../timerScreen/viewModel/TimerViewModel.kt | 2 +- 8 files changed, 52 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt b/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt index d7aea1a..89f27af 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt @@ -21,6 +21,7 @@ import android.net.Uri import android.provider.Settings import androidx.compose.material3.ColorScheme import androidx.compose.material3.lightColorScheme +import kotlinx.coroutines.flow.MutableStateFlow /** * Interface that holds the timer durations for each timer type. This repository maintains a single @@ -43,7 +44,7 @@ interface TimerRepository { var alarmSoundUri: Uri? - var serviceRunning: Boolean + var serviceRunning: MutableStateFlow } /** @@ -61,5 +62,5 @@ class AppTimerRepository : TimerRepository { override var colorScheme = lightColorScheme() override var alarmSoundUri: Uri? = Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI - override var serviceRunning = false + override var serviceRunning = MutableStateFlow(false) } \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt b/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt index 01552a2..e87e2a7 100644 --- a/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt +++ b/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt @@ -98,12 +98,12 @@ class TimerService : Service() { override fun onCreate() { super.onCreate() - timerRepository.serviceRunning = true + timerRepository.serviceRunning.update { true } alarm = initializeMediaPlayer() } override fun onDestroy() { - timerRepository.serviceRunning = false + timerRepository.serviceRunning.update { false } runBlocking { job.cancel() saveTimeToDb() diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt index f7c2c44..559142e 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt @@ -101,6 +101,7 @@ fun SettingsScreenRoot( val longBreakTimeInputFieldState = viewModel.longBreakTimeTextFieldState val isPlus by viewModel.isPlus.collectAsStateWithLifecycle() + val serviceRunning by viewModel.serviceRunning.collectAsStateWithLifecycle() val settingsState by viewModel.settingsState.collectAsStateWithLifecycle() @@ -115,6 +116,7 @@ fun SettingsScreenRoot( SettingsScreen( isPlus = isPlus, + serviceRunning = serviceRunning, settingsState = settingsState, backStack = backStack, focusTimeInputFieldState = focusTimeInputFieldState, @@ -132,6 +134,7 @@ fun SettingsScreenRoot( @Composable private fun SettingsScreen( isPlus: Boolean, + serviceRunning: Boolean, settingsState: SettingsState, backStack: SnapshotStateList, focusTimeInputFieldState: TextFieldState, @@ -292,6 +295,7 @@ private fun SettingsScreen( entry { TimerSettings( isPlus = isPlus, + serviceRunning = serviceRunning, settingsState = settingsState, focusTimeInputFieldState = focusTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsSwitchItem.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsSwitchItem.kt index 75638e3..f16c2f4 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsSwitchItem.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsSwitchItem.kt @@ -22,6 +22,7 @@ import androidx.annotation.StringRes data class SettingsSwitchItem( val checked: Boolean, + val enabled: Boolean = true, @param:DrawableRes val icon: Int, @param:StringRes val label: Int, @param:StringRes val description: Int, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/MinuteInputField.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/MinuteInputField.kt index f5f0c23..f530eea 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/MinuteInputField.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/MinuteInputField.kt @@ -46,12 +46,14 @@ import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors @Composable fun MinuteInputField( state: TextFieldState, + enabled: Boolean, shape: Shape, modifier: Modifier = Modifier, imeAction: ImeAction = ImeAction.Next ) { BasicTextField( state = state, + enabled = enabled, lineLimits = TextFieldLineLimits.SingleLine, inputTransformation = MinutesInputTransformation, // outputTransformation = MinutesOutputTransformation, @@ -63,7 +65,7 @@ fun MinuteInputField( fontFamily = interClock, fontSize = 57.sp, letterSpacing = (-2).sp, - color = colorScheme.onSurfaceVariant, + color = if (enabled) colorScheme.onSurfaceVariant else colorScheme.outlineVariant, textAlign = TextAlign.Center ), cursorBrush = SolidColor(colorScheme.onSurface), diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt index a7cb380..0d28931 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt @@ -50,6 +50,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.LargeFlexibleTopAppBar import androidx.compose.material3.ListItem +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.Slider @@ -60,7 +61,8 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberSliderState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -76,6 +78,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.nsh07.pomodoro.R +import org.nsh07.pomodoro.service.TimerService import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem import org.nsh07.pomodoro.ui.settingsScreen.components.MinuteInputField import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider @@ -95,6 +98,7 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape @Composable fun TimerSettings( isPlus: Boolean, + serviceRunning: Boolean, settingsState: SettingsState, focusTimeInputFieldState: TextFieldState, shortBreakTimeInputFieldState: TextFieldState, @@ -111,14 +115,21 @@ fun TimerSettings( val notificationManagerService = remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } - LaunchedEffect(Unit) { - if (!notificationManagerService.isNotificationPolicyAccessGranted()) - onAction(SettingsAction.SaveDndEnabled(false)) + DisposableEffect(Unit) { + onDispose { + if (!serviceRunning) { + Intent(context, TimerService::class.java).also { + it.action = TimerService.Actions.RESET.toString() + context.startService(it) + } + } + } } val switchItems = listOf( SettingsSwitchItem( checked = settingsState.dndEnabled, + enabled = !serviceRunning, icon = R.drawable.dnd, label = R.string.dnd, description = R.string.dnd_desc, @@ -171,6 +182,20 @@ fun TimerSettings( .padding(horizontal = 16.dp) ) { item { + CompositionLocalProvider(LocalContentColor provides colorScheme.error) { + AnimatedVisibility(serviceRunning) { + Column { + Spacer(Modifier.height(8.dp)) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon(painterResource(R.drawable.info), null) + Text("Reset the timer to change settings") + } + } + } + } Spacer(Modifier.height(14.dp)) } item { @@ -190,6 +215,7 @@ fun TimerSettings( ) MinuteInputField( state = focusTimeInputFieldState, + enabled = !serviceRunning, shape = RoundedCornerShape( topStart = topListItemShape.topStart, bottomStart = topListItemShape.topStart, @@ -210,6 +236,7 @@ fun TimerSettings( ) MinuteInputField( state = shortBreakTimeInputFieldState, + enabled = !serviceRunning, shape = RoundedCornerShape(middleListItemShape.topStart), imeAction = ImeAction.Next ) @@ -225,6 +252,7 @@ fun TimerSettings( ) MinuteInputField( state = longBreakTimeInputFieldState, + enabled = !serviceRunning, shape = RoundedCornerShape( topStart = bottomListItemShape.topStart, bottomStart = bottomListItemShape.topStart, @@ -257,6 +285,7 @@ fun TimerSettings( ) Slider( state = sessionsSliderState, + enabled = !serviceRunning, modifier = Modifier.padding(vertical = 4.dp) ) } @@ -281,6 +310,7 @@ fun TimerSettings( trailingContent = { Switch( checked = item.checked, + enabled = item.enabled, onCheckedChange = { item.onClick(it) }, thumbContent = { if (item.checked) { @@ -405,6 +435,7 @@ private fun TimerSettingsPreview() { ) TimerSettings( isPlus = false, + serviceRunning = true, settingsState = remember { SettingsState() }, focusTimeInputFieldState = focusTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt index d412bdd..a61146f 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt @@ -54,6 +54,7 @@ class SettingsViewModel( val backStack = mutableStateListOf(Screen.Settings.Main) val isPlus = billingManager.isPlus + val serviceRunning = timerRepository.serviceRunning.asStateFlow() private val _settingsState = MutableStateFlow(SettingsState()) val settingsState = _settingsState.asStateFlow() @@ -273,14 +274,14 @@ class SettingsViewModel( val Factory: ViewModelProvider.Factory = viewModelFactory { initializer { val application = (this[APPLICATION_KEY] as TomatoApplication) + val appBillingManager = application.container.billingManager val appPreferenceRepository = application.container.appPreferenceRepository val appTimerRepository = application.container.appTimerRepository - val appBillingManager = application.container.billingManager SettingsViewModel( billingManager = appBillingManager, preferenceRepository = appPreferenceRepository, - timerRepository = appTimerRepository, + timerRepository = appTimerRepository ) } } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt index 84c6ed4..6fefa40 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt @@ -70,7 +70,7 @@ class TimerViewModel( private var pauseDuration = 0L init { - if (!timerRepository.serviceRunning) + if (!timerRepository.serviceRunning.value) viewModelScope.launch(Dispatchers.IO) { timerRepository.focusTime = preferenceRepository.getIntPreference("focus_time")?.toLong()