Skip to content

Commit 5cb864f

Browse files
committed
feat(settings): improve auto-reload behaviour
1 parent 9ec3e68 commit 5cb864f

File tree

7 files changed

+115
-49
lines changed

7 files changed

+115
-49
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(
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/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/screens/TimerSettings.kt

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ import androidx.compose.material3.TopAppBarDefaults
6262
import androidx.compose.material3.rememberSliderState
6363
import androidx.compose.runtime.Composable
6464
import androidx.compose.runtime.CompositionLocalProvider
65-
import androidx.compose.runtime.DisposableEffect
6665
import androidx.compose.runtime.getValue
6766
import androidx.compose.runtime.mutableStateOf
6867
import androidx.compose.runtime.remember
@@ -78,7 +77,6 @@ import androidx.compose.ui.text.input.ImeAction
7877
import androidx.compose.ui.tooling.preview.Preview
7978
import androidx.compose.ui.unit.dp
8079
import org.nsh07.pomodoro.R
81-
import org.nsh07.pomodoro.service.TimerService
8280
import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem
8381
import org.nsh07.pomodoro.ui.settingsScreen.components.MinuteInputField
8482
import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider
@@ -115,17 +113,6 @@ fun TimerSettings(
115113
val notificationManagerService =
116114
remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
117115

118-
DisposableEffect(Unit) {
119-
onDispose {
120-
if (!serviceRunning) {
121-
Intent(context, TimerService::class.java).also {
122-
it.action = TimerService.Actions.RESET.toString()
123-
context.startService(it)
124-
}
125-
}
126-
}
127-
}
128-
129116
val switchItems = listOf(
130117
SettingsSwitchItem(
131118
checked = settingsState.dndEnabled,
@@ -191,7 +178,7 @@ fun TimerSettings(
191178
horizontalArrangement = Arrangement.spacedBy(8.dp)
192179
) {
193180
Icon(painterResource(R.drawable.info), null)
194-
Text("Reset the timer to change settings")
181+
Text(stringResource(R.string.timer_settings_reset_info))
195182
}
196183
}
197184
}

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,21 @@ import org.nsh07.pomodoro.TomatoApplication
4343
import org.nsh07.pomodoro.billing.BillingManager
4444
import org.nsh07.pomodoro.data.AppPreferenceRepository
4545
import org.nsh07.pomodoro.data.TimerRepository
46+
import org.nsh07.pomodoro.service.ServiceHelper
4647
import org.nsh07.pomodoro.ui.Screen
48+
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction
49+
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerMode
50+
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
51+
import org.nsh07.pomodoro.utils.millisecondsToStr
4752

4853
@OptIn(FlowPreview::class, ExperimentalMaterial3Api::class)
4954
class SettingsViewModel(
5055
private val billingManager: BillingManager,
5156
private val preferenceRepository: AppPreferenceRepository,
57+
private val serviceHelper: ServiceHelper,
58+
private val time: MutableStateFlow<Long>,
5259
private val timerRepository: TimerRepository,
60+
private val timerState: MutableStateFlow<TimerState>
5361
) : ViewModel() {
5462
val backStack = mutableStateListOf<Screen.Settings>(Screen.Settings.Main)
5563

@@ -107,6 +115,7 @@ class SettingsViewModel(
107115
"session_length",
108116
sessionsSliderState.value.toInt()
109117
)
118+
refreshTimer()
110119
}
111120
}
112121

@@ -117,6 +126,7 @@ class SettingsViewModel(
117126
.collect {
118127
if (it.isNotEmpty()) {
119128
timerRepository.focusTime = it.toString().toLong() * 60 * 1000
129+
refreshTimer()
120130
preferenceRepository.saveIntPreference(
121131
"focus_time",
122132
timerRepository.focusTime.toInt()
@@ -130,6 +140,7 @@ class SettingsViewModel(
130140
.collect {
131141
if (it.isNotEmpty()) {
132142
timerRepository.shortBreakTime = it.toString().toLong() * 60 * 1000
143+
refreshTimer()
133144
preferenceRepository.saveIntPreference(
134145
"short_break_time",
135146
timerRepository.shortBreakTime.toInt()
@@ -143,6 +154,7 @@ class SettingsViewModel(
143154
.collect {
144155
if (it.isNotEmpty()) {
145156
timerRepository.longBreakTime = it.toString().toLong() * 60 * 1000
157+
refreshTimer()
146158
preferenceRepository.saveIntPreference(
147159
"long_break_time",
148160
timerRepository.longBreakTime.toInt()
@@ -153,6 +165,7 @@ class SettingsViewModel(
153165
}
154166

155167
fun cancelTextFieldFlowCollection() {
168+
if (!serviceRunning.value) serviceHelper.startService(TimerAction.ResetTimer)
156169
focusFlowCollectionJob?.cancel()
157170
shortBreakFlowCollectionJob?.cancel()
158171
longBreakFlowCollectionJob?.cancel()
@@ -270,18 +283,42 @@ class SettingsViewModel(
270283
}
271284
}
272285

286+
private fun refreshTimer() {
287+
if (!serviceRunning.value) {
288+
time.update { timerRepository.focusTime }
289+
290+
timerState.update { currentState ->
291+
currentState.copy(
292+
timerMode = TimerMode.FOCUS,
293+
timeStr = millisecondsToStr(time.value),
294+
totalTime = time.value,
295+
nextTimerMode = if (timerRepository.sessionLength > 1) TimerMode.SHORT_BREAK else TimerMode.LONG_BREAK,
296+
nextTimeStr = millisecondsToStr(if (timerRepository.sessionLength > 1) timerRepository.shortBreakTime else timerRepository.longBreakTime),
297+
currentFocusCount = 1,
298+
totalFocusCount = timerRepository.sessionLength
299+
)
300+
}
301+
}
302+
}
303+
273304
companion object {
274305
val Factory: ViewModelProvider.Factory = viewModelFactory {
275306
initializer {
276307
val application = (this[APPLICATION_KEY] as TomatoApplication)
277308
val appBillingManager = application.container.billingManager
278309
val appPreferenceRepository = application.container.appPreferenceRepository
279310
val appTimerRepository = application.container.appTimerRepository
311+
val serviceHelper = application.container.serviceHelper
312+
val time = application.container.time
313+
val timerState = application.container.timerState
280314

281315
SettingsViewModel(
282316
billingManager = appBillingManager,
283317
preferenceRepository = appPreferenceRepository,
284-
timerRepository = appTimerRepository
318+
serviceHelper = serviceHelper,
319+
time = time,
320+
timerRepository = appTimerRepository,
321+
timerState = timerState
285322
)
286323
}
287324
}

app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@
1717

1818
package org.nsh07.pomodoro.ui.timerScreen.viewModel
1919

20-
import android.app.Application
2120
import android.provider.Settings
2221
import androidx.core.net.toUri
23-
import androidx.lifecycle.AndroidViewModel
22+
import androidx.lifecycle.ViewModel
2423
import androidx.lifecycle.ViewModelProvider
2524
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
2625
import androidx.lifecycle.viewModelScope
@@ -42,19 +41,20 @@ import org.nsh07.pomodoro.data.PreferenceRepository
4241
import org.nsh07.pomodoro.data.Stat
4342
import org.nsh07.pomodoro.data.StatRepository
4443
import org.nsh07.pomodoro.data.TimerRepository
44+
import org.nsh07.pomodoro.service.ServiceHelper
4545
import org.nsh07.pomodoro.utils.millisecondsToStr
4646
import java.time.LocalDate
4747
import java.time.temporal.ChronoUnit
4848

4949
@OptIn(FlowPreview::class)
5050
class TimerViewModel(
51-
application: Application,
5251
private val preferenceRepository: PreferenceRepository,
52+
private val serviceHelper: ServiceHelper,
5353
private val statRepository: StatRepository,
5454
private val timerRepository: TimerRepository,
5555
private val _timerState: MutableStateFlow<TimerState>,
5656
private val _time: MutableStateFlow<Long>
57-
) : AndroidViewModel(application) {
57+
) : ViewModel() {
5858
val timerState: StateFlow<TimerState> = _timerState.asStateFlow()
5959

6060
val time: StateFlow<Long> = _time.asStateFlow()
@@ -155,19 +155,24 @@ class TimerViewModel(
155155
}
156156
}
157157

158+
fun onAction(action: TimerAction) {
159+
serviceHelper.startService(action)
160+
}
161+
158162
companion object {
159163
val Factory: ViewModelProvider.Factory = viewModelFactory {
160164
initializer {
161165
val application = (this[APPLICATION_KEY] as TomatoApplication)
162166
val appPreferenceRepository = application.container.appPreferenceRepository
163167
val appStatRepository = application.container.appStatRepository
164168
val appTimerRepository = application.container.appTimerRepository
169+
val serviceHelper = application.container.serviceHelper
165170
val timerState = application.container.timerState
166171
val time = application.container.time
167172

168173
TimerViewModel(
169-
application = application,
170174
preferenceRepository = appPreferenceRepository,
175+
serviceHelper = serviceHelper,
171176
statRepository = appStatRepository,
172177
timerRepository = appTimerRepository,
173178
_timerState = timerState,

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,5 @@
9393
<string name="bmc">BuyMeACoffee</string>
9494
<string name="selected">Selected</string>
9595
<string name="help_with_translation">Help with translation</string>
96+
<string name="timer_settings_reset_info">Reset the timer to change settings</string>
9697
</resources>

0 commit comments

Comments
 (0)