Skip to content

Commit a8c96b8

Browse files
authored
Merge pull request #133 from nsh07/auto-timer-update
Auto timer update
2 parents f898556 + 5cb864f commit a8c96b8

File tree

12 files changed

+153
-47
lines changed

12 files changed

+153
-47
lines changed

app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
2929
import org.nsh07.pomodoro.R
3030
import org.nsh07.pomodoro.billing.BillingManager
3131
import org.nsh07.pomodoro.billing.BillingManagerProvider
32+
import org.nsh07.pomodoro.service.ServiceHelper
3233
import org.nsh07.pomodoro.service.addTimerActions
3334
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
3435
import org.nsh07.pomodoro.utils.millisecondsToStr
@@ -41,6 +42,7 @@ interface AppContainer {
4142
val notificationManager: NotificationManagerCompat
4243
val notificationManagerService: NotificationManager
4344
val notificationBuilder: NotificationCompat.Builder
45+
val serviceHelper: ServiceHelper
4446
val timerState: MutableStateFlow<TimerState>
4547
val time: MutableStateFlow<Long>
4648
var activityTurnScreenOn: (Boolean) -> Unit
@@ -87,6 +89,10 @@ class DefaultAppContainer(context: Context) : AppContainer {
8789
.setVisibility(VISIBILITY_PUBLIC)
8890
}
8991

92+
override val serviceHelper: ServiceHelper by lazy {
93+
ServiceHelper(context)
94+
}
95+
9096
override val timerState: MutableStateFlow<TimerState> by lazy {
9197
MutableStateFlow(
9298
TimerState(

app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import android.net.Uri
2121
import android.provider.Settings
2222
import androidx.compose.material3.ColorScheme
2323
import androidx.compose.material3.lightColorScheme
24+
import kotlinx.coroutines.flow.MutableStateFlow
2425

2526
/**
2627
* Interface that holds the timer durations for each timer type. This repository maintains a single
@@ -43,7 +44,7 @@ interface TimerRepository {
4344

4445
var alarmSoundUri: Uri?
4546

46-
var serviceRunning: Boolean
47+
var serviceRunning: MutableStateFlow<Boolean>
4748
}
4849

4950
/**
@@ -61,5 +62,5 @@ class AppTimerRepository : TimerRepository {
6162
override var colorScheme = lightColorScheme()
6263
override var alarmSoundUri: Uri? =
6364
Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI
64-
override var serviceRunning = false
65+
override var serviceRunning = MutableStateFlow(false)
6566
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (c) 2025 Nishant Mishra
3+
*
4+
* This file is part of Tomato - a minimalist pomodoro timer for Android.
5+
*
6+
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
7+
* General Public License as published by the Free Software Foundation, either version 3 of the
8+
* License, or (at your option) any later version.
9+
*
10+
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
11+
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12+
* Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License along with Tomato.
15+
* If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
package org.nsh07.pomodoro.service
19+
20+
import android.content.Context
21+
import android.content.Intent
22+
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction
23+
24+
/**
25+
* Helper class that holds a reference to [Context] and helps call [Context.startService] in
26+
* [androidx.lifecycle.ViewModel]s. This class must be managed by an [android.app.Application] class
27+
* to scope it to the Activity's lifecycle and prevent leaks.
28+
*/
29+
class ServiceHelper(private val context: Context) {
30+
fun startService(action: TimerAction) {
31+
when (action) {
32+
TimerAction.ResetTimer ->
33+
Intent(context, TimerService::class.java).also {
34+
it.action = TimerService.Actions.RESET.toString()
35+
context.startService(it)
36+
}
37+
38+
is TimerAction.SkipTimer ->
39+
Intent(context, TimerService::class.java).also {
40+
it.action = TimerService.Actions.SKIP.toString()
41+
context.startService(it)
42+
}
43+
44+
TimerAction.StopAlarm ->
45+
Intent(context, TimerService::class.java).also {
46+
it.action =
47+
TimerService.Actions.STOP_ALARM.toString()
48+
context.startService(it)
49+
}
50+
51+
TimerAction.ToggleTimer ->
52+
Intent(context, TimerService::class.java).also {
53+
it.action = TimerService.Actions.TOGGLE.toString()
54+
context.startService(it)
55+
}
56+
}
57+
}
58+
}

app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,12 @@ class TimerService : Service() {
9898

9999
override fun onCreate() {
100100
super.onCreate()
101-
timerRepository.serviceRunning = true
101+
timerRepository.serviceRunning.update { true }
102102
alarm = initializeMediaPlayer()
103103
}
104104

105105
override fun onDestroy() {
106-
timerRepository.serviceRunning = false
106+
timerRepository.serviceRunning.update { false }
107107
runBlocking {
108108
job.cancel()
109109
saveTimeToDb()

app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ import org.nsh07.pomodoro.ui.settingsScreen.SettingsScreenRoot
6363
import org.nsh07.pomodoro.ui.statsScreen.StatsScreenRoot
6464
import org.nsh07.pomodoro.ui.timerScreen.AlarmDialog
6565
import org.nsh07.pomodoro.ui.timerScreen.TimerScreen
66-
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction
6766
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerViewModel
6867

6968
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@@ -163,34 +162,7 @@ fun AppScreen(
163162
timerState = uiState,
164163
isPlus = isPlus,
165164
progress = { progress },
166-
onAction = { action ->
167-
when (action) {
168-
TimerAction.ResetTimer ->
169-
Intent(context, TimerService::class.java).also {
170-
it.action = TimerService.Actions.RESET.toString()
171-
context.startService(it)
172-
}
173-
174-
is TimerAction.SkipTimer ->
175-
Intent(context, TimerService::class.java).also {
176-
it.action = TimerService.Actions.SKIP.toString()
177-
context.startService(it)
178-
}
179-
180-
TimerAction.StopAlarm ->
181-
Intent(context, TimerService::class.java).also {
182-
it.action =
183-
TimerService.Actions.STOP_ALARM.toString()
184-
context.startService(it)
185-
}
186-
187-
TimerAction.ToggleTimer ->
188-
Intent(context, TimerService::class.java).also {
189-
it.action = TimerService.Actions.TOGGLE.toString()
190-
context.startService(it)
191-
}
192-
}
193-
},
165+
onAction = timerViewModel::onAction,
194166
modifier = modifier
195167
.padding(
196168
start = contentPadding.calculateStartPadding(layoutDirection),

app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ fun SettingsScreenRoot(
101101
val longBreakTimeInputFieldState = viewModel.longBreakTimeTextFieldState
102102

103103
val isPlus by viewModel.isPlus.collectAsStateWithLifecycle()
104+
val serviceRunning by viewModel.serviceRunning.collectAsStateWithLifecycle()
104105

105106
val settingsState by viewModel.settingsState.collectAsStateWithLifecycle()
106107

@@ -115,6 +116,7 @@ fun SettingsScreenRoot(
115116

116117
SettingsScreen(
117118
isPlus = isPlus,
119+
serviceRunning = serviceRunning,
118120
settingsState = settingsState,
119121
backStack = backStack,
120122
focusTimeInputFieldState = focusTimeInputFieldState,
@@ -132,6 +134,7 @@ fun SettingsScreenRoot(
132134
@Composable
133135
private fun SettingsScreen(
134136
isPlus: Boolean,
137+
serviceRunning: Boolean,
135138
settingsState: SettingsState,
136139
backStack: SnapshotStateList<Screen.Settings>,
137140
focusTimeInputFieldState: TextFieldState,
@@ -292,6 +295,7 @@ private fun SettingsScreen(
292295
entry<Screen.Settings.Timer> {
293296
TimerSettings(
294297
isPlus = isPlus,
298+
serviceRunning = serviceRunning,
295299
settingsState = settingsState,
296300
focusTimeInputFieldState = focusTimeInputFieldState,
297301
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,

app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsSwitchItem.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.annotation.StringRes
2222

2323
data class SettingsSwitchItem(
2424
val checked: Boolean,
25+
val enabled: Boolean = true,
2526
@param:DrawableRes val icon: Int,
2627
@param:StringRes val label: Int,
2728
@param:StringRes val description: Int,

app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/MinuteInputField.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@ import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
4646
@Composable
4747
fun MinuteInputField(
4848
state: TextFieldState,
49+
enabled: Boolean,
4950
shape: Shape,
5051
modifier: Modifier = Modifier,
5152
imeAction: ImeAction = ImeAction.Next
5253
) {
5354
BasicTextField(
5455
state = state,
56+
enabled = enabled,
5557
lineLimits = TextFieldLineLimits.SingleLine,
5658
inputTransformation = MinutesInputTransformation,
5759
// outputTransformation = MinutesOutputTransformation,
@@ -63,7 +65,7 @@ fun MinuteInputField(
6365
fontFamily = interClock,
6466
fontSize = 57.sp,
6567
letterSpacing = (-2).sp,
66-
color = colorScheme.onSurfaceVariant,
68+
color = if (enabled) colorScheme.onSurfaceVariant else colorScheme.outlineVariant,
6769
textAlign = TextAlign.Center
6870
),
6971
cursorBrush = SolidColor(colorScheme.onSurface),

app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import androidx.compose.material3.IconButton
5050
import androidx.compose.material3.IconButtonDefaults
5151
import androidx.compose.material3.LargeFlexibleTopAppBar
5252
import androidx.compose.material3.ListItem
53+
import androidx.compose.material3.LocalContentColor
5354
import androidx.compose.material3.MaterialTheme.colorScheme
5455
import androidx.compose.material3.MaterialTheme.typography
5556
import androidx.compose.material3.Slider
@@ -60,7 +61,7 @@ import androidx.compose.material3.Text
6061
import androidx.compose.material3.TopAppBarDefaults
6162
import androidx.compose.material3.rememberSliderState
6263
import androidx.compose.runtime.Composable
63-
import androidx.compose.runtime.LaunchedEffect
64+
import androidx.compose.runtime.CompositionLocalProvider
6465
import androidx.compose.runtime.getValue
6566
import androidx.compose.runtime.mutableStateOf
6667
import androidx.compose.runtime.remember
@@ -95,6 +96,7 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
9596
@Composable
9697
fun TimerSettings(
9798
isPlus: Boolean,
99+
serviceRunning: Boolean,
98100
settingsState: SettingsState,
99101
focusTimeInputFieldState: TextFieldState,
100102
shortBreakTimeInputFieldState: TextFieldState,
@@ -111,14 +113,10 @@ fun TimerSettings(
111113
val notificationManagerService =
112114
remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
113115

114-
LaunchedEffect(Unit) {
115-
if (!notificationManagerService.isNotificationPolicyAccessGranted())
116-
onAction(SettingsAction.SaveDndEnabled(false))
117-
}
118-
119116
val switchItems = listOf(
120117
SettingsSwitchItem(
121118
checked = settingsState.dndEnabled,
119+
enabled = !serviceRunning,
122120
icon = R.drawable.dnd,
123121
label = R.string.dnd,
124122
description = R.string.dnd_desc,
@@ -171,6 +169,20 @@ fun TimerSettings(
171169
.padding(horizontal = 16.dp)
172170
) {
173171
item {
172+
CompositionLocalProvider(LocalContentColor provides colorScheme.error) {
173+
AnimatedVisibility(serviceRunning) {
174+
Column {
175+
Spacer(Modifier.height(8.dp))
176+
Row(
177+
verticalAlignment = Alignment.CenterVertically,
178+
horizontalArrangement = Arrangement.spacedBy(8.dp)
179+
) {
180+
Icon(painterResource(R.drawable.info), null)
181+
Text(stringResource(R.string.timer_settings_reset_info))
182+
}
183+
}
184+
}
185+
}
174186
Spacer(Modifier.height(14.dp))
175187
}
176188
item {
@@ -190,6 +202,7 @@ fun TimerSettings(
190202
)
191203
MinuteInputField(
192204
state = focusTimeInputFieldState,
205+
enabled = !serviceRunning,
193206
shape = RoundedCornerShape(
194207
topStart = topListItemShape.topStart,
195208
bottomStart = topListItemShape.topStart,
@@ -210,6 +223,7 @@ fun TimerSettings(
210223
)
211224
MinuteInputField(
212225
state = shortBreakTimeInputFieldState,
226+
enabled = !serviceRunning,
213227
shape = RoundedCornerShape(middleListItemShape.topStart),
214228
imeAction = ImeAction.Next
215229
)
@@ -225,6 +239,7 @@ fun TimerSettings(
225239
)
226240
MinuteInputField(
227241
state = longBreakTimeInputFieldState,
242+
enabled = !serviceRunning,
228243
shape = RoundedCornerShape(
229244
topStart = bottomListItemShape.topStart,
230245
bottomStart = bottomListItemShape.topStart,
@@ -257,6 +272,7 @@ fun TimerSettings(
257272
)
258273
Slider(
259274
state = sessionsSliderState,
275+
enabled = !serviceRunning,
260276
modifier = Modifier.padding(vertical = 4.dp)
261277
)
262278
}
@@ -281,6 +297,7 @@ fun TimerSettings(
281297
trailingContent = {
282298
Switch(
283299
checked = item.checked,
300+
enabled = item.enabled,
284301
onCheckedChange = { item.onClick(it) },
285302
thumbContent = {
286303
if (item.checked) {
@@ -405,6 +422,7 @@ private fun TimerSettingsPreview() {
405422
)
406423
TimerSettings(
407424
isPlus = false,
425+
serviceRunning = true,
408426
settingsState = remember { SettingsState() },
409427
focusTimeInputFieldState = focusTimeInputFieldState,
410428
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,

0 commit comments

Comments
 (0)