feat(settings): disable editing time when service is running, auto reload

when navigating to timer screen
This commit is contained in:
Nishant Mishra
2025-11-09 22:08:47 +05:30
parent f8985564f7
commit 9ec3e6851f
8 changed files with 52 additions and 12 deletions

View File

@@ -21,6 +21,7 @@ import android.net.Uri
import android.provider.Settings import android.provider.Settings
import androidx.compose.material3.ColorScheme import androidx.compose.material3.ColorScheme
import androidx.compose.material3.lightColorScheme 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 * 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 alarmSoundUri: Uri?
var serviceRunning: Boolean var serviceRunning: MutableStateFlow<Boolean>
} }
/** /**
@@ -61,5 +62,5 @@ class AppTimerRepository : TimerRepository {
override var colorScheme = lightColorScheme() override var colorScheme = lightColorScheme()
override var alarmSoundUri: Uri? = override var alarmSoundUri: Uri? =
Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI
override var serviceRunning = false override var serviceRunning = MutableStateFlow(false)
} }

View File

@@ -98,12 +98,12 @@ class TimerService : Service() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
timerRepository.serviceRunning = true timerRepository.serviceRunning.update { true }
alarm = initializeMediaPlayer() alarm = initializeMediaPlayer()
} }
override fun onDestroy() { override fun onDestroy() {
timerRepository.serviceRunning = false timerRepository.serviceRunning.update { false }
runBlocking { runBlocking {
job.cancel() job.cancel()
saveTimeToDb() saveTimeToDb()

View File

@@ -101,6 +101,7 @@ fun SettingsScreenRoot(
val longBreakTimeInputFieldState = viewModel.longBreakTimeTextFieldState val longBreakTimeInputFieldState = viewModel.longBreakTimeTextFieldState
val isPlus by viewModel.isPlus.collectAsStateWithLifecycle() val isPlus by viewModel.isPlus.collectAsStateWithLifecycle()
val serviceRunning by viewModel.serviceRunning.collectAsStateWithLifecycle()
val settingsState by viewModel.settingsState.collectAsStateWithLifecycle() val settingsState by viewModel.settingsState.collectAsStateWithLifecycle()
@@ -115,6 +116,7 @@ fun SettingsScreenRoot(
SettingsScreen( SettingsScreen(
isPlus = isPlus, isPlus = isPlus,
serviceRunning = serviceRunning,
settingsState = settingsState, settingsState = settingsState,
backStack = backStack, backStack = backStack,
focusTimeInputFieldState = focusTimeInputFieldState, focusTimeInputFieldState = focusTimeInputFieldState,
@@ -132,6 +134,7 @@ fun SettingsScreenRoot(
@Composable @Composable
private fun SettingsScreen( private fun SettingsScreen(
isPlus: Boolean, isPlus: Boolean,
serviceRunning: Boolean,
settingsState: SettingsState, settingsState: SettingsState,
backStack: SnapshotStateList<Screen.Settings>, backStack: SnapshotStateList<Screen.Settings>,
focusTimeInputFieldState: TextFieldState, focusTimeInputFieldState: TextFieldState,
@@ -292,6 +295,7 @@ private fun SettingsScreen(
entry<Screen.Settings.Timer> { entry<Screen.Settings.Timer> {
TimerSettings( TimerSettings(
isPlus = isPlus, isPlus = isPlus,
serviceRunning = serviceRunning,
settingsState = settingsState, settingsState = settingsState,
focusTimeInputFieldState = focusTimeInputFieldState, focusTimeInputFieldState = focusTimeInputFieldState,
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,

View File

@@ -22,6 +22,7 @@ import androidx.annotation.StringRes
data class SettingsSwitchItem( data class SettingsSwitchItem(
val checked: Boolean, val checked: Boolean,
val enabled: Boolean = true,
@param:DrawableRes val icon: Int, @param:DrawableRes val icon: Int,
@param:StringRes val label: Int, @param:StringRes val label: Int,
@param:StringRes val description: Int, @param:StringRes val description: Int,

View File

@@ -46,12 +46,14 @@ import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
@Composable @Composable
fun MinuteInputField( fun MinuteInputField(
state: TextFieldState, state: TextFieldState,
enabled: Boolean,
shape: Shape, shape: Shape,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
imeAction: ImeAction = ImeAction.Next imeAction: ImeAction = ImeAction.Next
) { ) {
BasicTextField( BasicTextField(
state = state, state = state,
enabled = enabled,
lineLimits = TextFieldLineLimits.SingleLine, lineLimits = TextFieldLineLimits.SingleLine,
inputTransformation = MinutesInputTransformation, inputTransformation = MinutesInputTransformation,
// outputTransformation = MinutesOutputTransformation, // outputTransformation = MinutesOutputTransformation,
@@ -63,7 +65,7 @@ fun MinuteInputField(
fontFamily = interClock, fontFamily = interClock,
fontSize = 57.sp, fontSize = 57.sp,
letterSpacing = (-2).sp, letterSpacing = (-2).sp,
color = colorScheme.onSurfaceVariant, color = if (enabled) colorScheme.onSurfaceVariant else colorScheme.outlineVariant,
textAlign = TextAlign.Center textAlign = TextAlign.Center
), ),
cursorBrush = SolidColor(colorScheme.onSurface), cursorBrush = SolidColor(colorScheme.onSurface),

View File

@@ -50,6 +50,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LargeFlexibleTopAppBar import androidx.compose.material3.LargeFlexibleTopAppBar
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Slider import androidx.compose.material3.Slider
@@ -60,7 +61,8 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberSliderState import androidx.compose.material3.rememberSliderState
import androidx.compose.runtime.Composable 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.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.nsh07.pomodoro.R import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.service.TimerService
import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem
import org.nsh07.pomodoro.ui.settingsScreen.components.MinuteInputField import org.nsh07.pomodoro.ui.settingsScreen.components.MinuteInputField
import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider
@@ -95,6 +98,7 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
@Composable @Composable
fun TimerSettings( fun TimerSettings(
isPlus: Boolean, isPlus: Boolean,
serviceRunning: Boolean,
settingsState: SettingsState, settingsState: SettingsState,
focusTimeInputFieldState: TextFieldState, focusTimeInputFieldState: TextFieldState,
shortBreakTimeInputFieldState: TextFieldState, shortBreakTimeInputFieldState: TextFieldState,
@@ -111,14 +115,21 @@ fun TimerSettings(
val notificationManagerService = val notificationManagerService =
remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
LaunchedEffect(Unit) { DisposableEffect(Unit) {
if (!notificationManagerService.isNotificationPolicyAccessGranted()) onDispose {
onAction(SettingsAction.SaveDndEnabled(false)) if (!serviceRunning) {
Intent(context, TimerService::class.java).also {
it.action = TimerService.Actions.RESET.toString()
context.startService(it)
}
}
}
} }
val switchItems = listOf( val switchItems = listOf(
SettingsSwitchItem( SettingsSwitchItem(
checked = settingsState.dndEnabled, checked = settingsState.dndEnabled,
enabled = !serviceRunning,
icon = R.drawable.dnd, icon = R.drawable.dnd,
label = R.string.dnd, label = R.string.dnd,
description = R.string.dnd_desc, description = R.string.dnd_desc,
@@ -171,6 +182,20 @@ fun TimerSettings(
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
) { ) {
item { 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)) Spacer(Modifier.height(14.dp))
} }
item { item {
@@ -190,6 +215,7 @@ fun TimerSettings(
) )
MinuteInputField( MinuteInputField(
state = focusTimeInputFieldState, state = focusTimeInputFieldState,
enabled = !serviceRunning,
shape = RoundedCornerShape( shape = RoundedCornerShape(
topStart = topListItemShape.topStart, topStart = topListItemShape.topStart,
bottomStart = topListItemShape.topStart, bottomStart = topListItemShape.topStart,
@@ -210,6 +236,7 @@ fun TimerSettings(
) )
MinuteInputField( MinuteInputField(
state = shortBreakTimeInputFieldState, state = shortBreakTimeInputFieldState,
enabled = !serviceRunning,
shape = RoundedCornerShape(middleListItemShape.topStart), shape = RoundedCornerShape(middleListItemShape.topStart),
imeAction = ImeAction.Next imeAction = ImeAction.Next
) )
@@ -225,6 +252,7 @@ fun TimerSettings(
) )
MinuteInputField( MinuteInputField(
state = longBreakTimeInputFieldState, state = longBreakTimeInputFieldState,
enabled = !serviceRunning,
shape = RoundedCornerShape( shape = RoundedCornerShape(
topStart = bottomListItemShape.topStart, topStart = bottomListItemShape.topStart,
bottomStart = bottomListItemShape.topStart, bottomStart = bottomListItemShape.topStart,
@@ -257,6 +285,7 @@ fun TimerSettings(
) )
Slider( Slider(
state = sessionsSliderState, state = sessionsSliderState,
enabled = !serviceRunning,
modifier = Modifier.padding(vertical = 4.dp) modifier = Modifier.padding(vertical = 4.dp)
) )
} }
@@ -281,6 +310,7 @@ fun TimerSettings(
trailingContent = { trailingContent = {
Switch( Switch(
checked = item.checked, checked = item.checked,
enabled = item.enabled,
onCheckedChange = { item.onClick(it) }, onCheckedChange = { item.onClick(it) },
thumbContent = { thumbContent = {
if (item.checked) { if (item.checked) {
@@ -405,6 +435,7 @@ private fun TimerSettingsPreview() {
) )
TimerSettings( TimerSettings(
isPlus = false, isPlus = false,
serviceRunning = true,
settingsState = remember { SettingsState() }, settingsState = remember { SettingsState() },
focusTimeInputFieldState = focusTimeInputFieldState, focusTimeInputFieldState = focusTimeInputFieldState,
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,

View File

@@ -54,6 +54,7 @@ class SettingsViewModel(
val backStack = mutableStateListOf<Screen.Settings>(Screen.Settings.Main) val backStack = mutableStateListOf<Screen.Settings>(Screen.Settings.Main)
val isPlus = billingManager.isPlus val isPlus = billingManager.isPlus
val serviceRunning = timerRepository.serviceRunning.asStateFlow()
private val _settingsState = MutableStateFlow(SettingsState()) private val _settingsState = MutableStateFlow(SettingsState())
val settingsState = _settingsState.asStateFlow() val settingsState = _settingsState.asStateFlow()
@@ -273,14 +274,14 @@ class SettingsViewModel(
val Factory: ViewModelProvider.Factory = viewModelFactory { val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer { initializer {
val application = (this[APPLICATION_KEY] as TomatoApplication) val application = (this[APPLICATION_KEY] as TomatoApplication)
val appBillingManager = application.container.billingManager
val appPreferenceRepository = application.container.appPreferenceRepository val appPreferenceRepository = application.container.appPreferenceRepository
val appTimerRepository = application.container.appTimerRepository val appTimerRepository = application.container.appTimerRepository
val appBillingManager = application.container.billingManager
SettingsViewModel( SettingsViewModel(
billingManager = appBillingManager, billingManager = appBillingManager,
preferenceRepository = appPreferenceRepository, preferenceRepository = appPreferenceRepository,
timerRepository = appTimerRepository, timerRepository = appTimerRepository
) )
} }
} }

View File

@@ -70,7 +70,7 @@ class TimerViewModel(
private var pauseDuration = 0L private var pauseDuration = 0L
init { init {
if (!timerRepository.serviceRunning) if (!timerRepository.serviceRunning.value)
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
timerRepository.focusTime = timerRepository.focusTime =
preferenceRepository.getIntPreference("focus_time")?.toLong() preferenceRepository.getIntPreference("focus_time")?.toLong()