feat(settings): improve auto-reload behaviour
This commit is contained in:
@@ -29,6 +29,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.nsh07.pomodoro.R
|
||||
import org.nsh07.pomodoro.billing.BillingManager
|
||||
import org.nsh07.pomodoro.billing.BillingManagerProvider
|
||||
import org.nsh07.pomodoro.service.ServiceHelper
|
||||
import org.nsh07.pomodoro.service.addTimerActions
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
|
||||
import org.nsh07.pomodoro.utils.millisecondsToStr
|
||||
@@ -41,6 +42,7 @@ interface AppContainer {
|
||||
val notificationManager: NotificationManagerCompat
|
||||
val notificationManagerService: NotificationManager
|
||||
val notificationBuilder: NotificationCompat.Builder
|
||||
val serviceHelper: ServiceHelper
|
||||
val timerState: MutableStateFlow<TimerState>
|
||||
val time: MutableStateFlow<Long>
|
||||
var activityTurnScreenOn: (Boolean) -> Unit
|
||||
@@ -87,6 +89,10 @@ class DefaultAppContainer(context: Context) : AppContainer {
|
||||
.setVisibility(VISIBILITY_PUBLIC)
|
||||
}
|
||||
|
||||
override val serviceHelper: ServiceHelper by lazy {
|
||||
ServiceHelper(context)
|
||||
}
|
||||
|
||||
override val timerState: MutableStateFlow<TimerState> by lazy {
|
||||
MutableStateFlow(
|
||||
TimerState(
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.service
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction
|
||||
|
||||
/**
|
||||
* Helper class that holds a reference to [Context] and helps call [Context.startService] in
|
||||
* [androidx.lifecycle.ViewModel]s. This class must be managed by an [android.app.Application] class
|
||||
* to scope it to the Activity's lifecycle and prevent leaks.
|
||||
*/
|
||||
class ServiceHelper(private val context: Context) {
|
||||
fun startService(action: TimerAction) {
|
||||
when (action) {
|
||||
TimerAction.ResetTimer ->
|
||||
Intent(context, TimerService::class.java).also {
|
||||
it.action = TimerService.Actions.RESET.toString()
|
||||
context.startService(it)
|
||||
}
|
||||
|
||||
is TimerAction.SkipTimer ->
|
||||
Intent(context, TimerService::class.java).also {
|
||||
it.action = TimerService.Actions.SKIP.toString()
|
||||
context.startService(it)
|
||||
}
|
||||
|
||||
TimerAction.StopAlarm ->
|
||||
Intent(context, TimerService::class.java).also {
|
||||
it.action =
|
||||
TimerService.Actions.STOP_ALARM.toString()
|
||||
context.startService(it)
|
||||
}
|
||||
|
||||
TimerAction.ToggleTimer ->
|
||||
Intent(context, TimerService::class.java).also {
|
||||
it.action = TimerService.Actions.TOGGLE.toString()
|
||||
context.startService(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,6 @@ import org.nsh07.pomodoro.ui.settingsScreen.SettingsScreenRoot
|
||||
import org.nsh07.pomodoro.ui.statsScreen.StatsScreenRoot
|
||||
import org.nsh07.pomodoro.ui.timerScreen.AlarmDialog
|
||||
import org.nsh07.pomodoro.ui.timerScreen.TimerScreen
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@@ -163,34 +162,7 @@ fun AppScreen(
|
||||
timerState = uiState,
|
||||
isPlus = isPlus,
|
||||
progress = { progress },
|
||||
onAction = { action ->
|
||||
when (action) {
|
||||
TimerAction.ResetTimer ->
|
||||
Intent(context, TimerService::class.java).also {
|
||||
it.action = TimerService.Actions.RESET.toString()
|
||||
context.startService(it)
|
||||
}
|
||||
|
||||
is TimerAction.SkipTimer ->
|
||||
Intent(context, TimerService::class.java).also {
|
||||
it.action = TimerService.Actions.SKIP.toString()
|
||||
context.startService(it)
|
||||
}
|
||||
|
||||
TimerAction.StopAlarm ->
|
||||
Intent(context, TimerService::class.java).also {
|
||||
it.action =
|
||||
TimerService.Actions.STOP_ALARM.toString()
|
||||
context.startService(it)
|
||||
}
|
||||
|
||||
TimerAction.ToggleTimer ->
|
||||
Intent(context, TimerService::class.java).also {
|
||||
it.action = TimerService.Actions.TOGGLE.toString()
|
||||
context.startService(it)
|
||||
}
|
||||
}
|
||||
},
|
||||
onAction = timerViewModel::onAction,
|
||||
modifier = modifier
|
||||
.padding(
|
||||
start = contentPadding.calculateStartPadding(layoutDirection),
|
||||
|
||||
@@ -62,7 +62,6 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberSliderState
|
||||
import androidx.compose.runtime.Composable
|
||||
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
|
||||
@@ -78,7 +77,6 @@ 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
|
||||
@@ -115,17 +113,6 @@ fun TimerSettings(
|
||||
val notificationManagerService =
|
||||
remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
|
||||
|
||||
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,
|
||||
@@ -191,7 +178,7 @@ fun TimerSettings(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Icon(painterResource(R.drawable.info), null)
|
||||
Text("Reset the timer to change settings")
|
||||
Text(stringResource(R.string.timer_settings_reset_info))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,21 @@ import org.nsh07.pomodoro.TomatoApplication
|
||||
import org.nsh07.pomodoro.billing.BillingManager
|
||||
import org.nsh07.pomodoro.data.AppPreferenceRepository
|
||||
import org.nsh07.pomodoro.data.TimerRepository
|
||||
import org.nsh07.pomodoro.service.ServiceHelper
|
||||
import org.nsh07.pomodoro.ui.Screen
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerMode
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
|
||||
import org.nsh07.pomodoro.utils.millisecondsToStr
|
||||
|
||||
@OptIn(FlowPreview::class, ExperimentalMaterial3Api::class)
|
||||
class SettingsViewModel(
|
||||
private val billingManager: BillingManager,
|
||||
private val preferenceRepository: AppPreferenceRepository,
|
||||
private val serviceHelper: ServiceHelper,
|
||||
private val time: MutableStateFlow<Long>,
|
||||
private val timerRepository: TimerRepository,
|
||||
private val timerState: MutableStateFlow<TimerState>
|
||||
) : ViewModel() {
|
||||
val backStack = mutableStateListOf<Screen.Settings>(Screen.Settings.Main)
|
||||
|
||||
@@ -107,6 +115,7 @@ class SettingsViewModel(
|
||||
"session_length",
|
||||
sessionsSliderState.value.toInt()
|
||||
)
|
||||
refreshTimer()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +126,7 @@ class SettingsViewModel(
|
||||
.collect {
|
||||
if (it.isNotEmpty()) {
|
||||
timerRepository.focusTime = it.toString().toLong() * 60 * 1000
|
||||
refreshTimer()
|
||||
preferenceRepository.saveIntPreference(
|
||||
"focus_time",
|
||||
timerRepository.focusTime.toInt()
|
||||
@@ -130,6 +140,7 @@ class SettingsViewModel(
|
||||
.collect {
|
||||
if (it.isNotEmpty()) {
|
||||
timerRepository.shortBreakTime = it.toString().toLong() * 60 * 1000
|
||||
refreshTimer()
|
||||
preferenceRepository.saveIntPreference(
|
||||
"short_break_time",
|
||||
timerRepository.shortBreakTime.toInt()
|
||||
@@ -143,6 +154,7 @@ class SettingsViewModel(
|
||||
.collect {
|
||||
if (it.isNotEmpty()) {
|
||||
timerRepository.longBreakTime = it.toString().toLong() * 60 * 1000
|
||||
refreshTimer()
|
||||
preferenceRepository.saveIntPreference(
|
||||
"long_break_time",
|
||||
timerRepository.longBreakTime.toInt()
|
||||
@@ -153,6 +165,7 @@ class SettingsViewModel(
|
||||
}
|
||||
|
||||
fun cancelTextFieldFlowCollection() {
|
||||
if (!serviceRunning.value) serviceHelper.startService(TimerAction.ResetTimer)
|
||||
focusFlowCollectionJob?.cancel()
|
||||
shortBreakFlowCollectionJob?.cancel()
|
||||
longBreakFlowCollectionJob?.cancel()
|
||||
@@ -270,6 +283,24 @@ class SettingsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshTimer() {
|
||||
if (!serviceRunning.value) {
|
||||
time.update { timerRepository.focusTime }
|
||||
|
||||
timerState.update { currentState ->
|
||||
currentState.copy(
|
||||
timerMode = TimerMode.FOCUS,
|
||||
timeStr = millisecondsToStr(time.value),
|
||||
totalTime = time.value,
|
||||
nextTimerMode = if (timerRepository.sessionLength > 1) TimerMode.SHORT_BREAK else TimerMode.LONG_BREAK,
|
||||
nextTimeStr = millisecondsToStr(if (timerRepository.sessionLength > 1) timerRepository.shortBreakTime else timerRepository.longBreakTime),
|
||||
currentFocusCount = 1,
|
||||
totalFocusCount = timerRepository.sessionLength
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
@@ -277,11 +308,17 @@ class SettingsViewModel(
|
||||
val appBillingManager = application.container.billingManager
|
||||
val appPreferenceRepository = application.container.appPreferenceRepository
|
||||
val appTimerRepository = application.container.appTimerRepository
|
||||
val serviceHelper = application.container.serviceHelper
|
||||
val time = application.container.time
|
||||
val timerState = application.container.timerState
|
||||
|
||||
SettingsViewModel(
|
||||
billingManager = appBillingManager,
|
||||
preferenceRepository = appPreferenceRepository,
|
||||
timerRepository = appTimerRepository
|
||||
serviceHelper = serviceHelper,
|
||||
time = time,
|
||||
timerRepository = appTimerRepository,
|
||||
timerState = timerState
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,9 @@
|
||||
|
||||
package org.nsh07.pomodoro.ui.timerScreen.viewModel
|
||||
|
||||
import android.app.Application
|
||||
import android.provider.Settings
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@@ -42,19 +41,20 @@ import org.nsh07.pomodoro.data.PreferenceRepository
|
||||
import org.nsh07.pomodoro.data.Stat
|
||||
import org.nsh07.pomodoro.data.StatRepository
|
||||
import org.nsh07.pomodoro.data.TimerRepository
|
||||
import org.nsh07.pomodoro.service.ServiceHelper
|
||||
import org.nsh07.pomodoro.utils.millisecondsToStr
|
||||
import java.time.LocalDate
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
class TimerViewModel(
|
||||
application: Application,
|
||||
private val preferenceRepository: PreferenceRepository,
|
||||
private val serviceHelper: ServiceHelper,
|
||||
private val statRepository: StatRepository,
|
||||
private val timerRepository: TimerRepository,
|
||||
private val _timerState: MutableStateFlow<TimerState>,
|
||||
private val _time: MutableStateFlow<Long>
|
||||
) : AndroidViewModel(application) {
|
||||
) : ViewModel() {
|
||||
val timerState: StateFlow<TimerState> = _timerState.asStateFlow()
|
||||
|
||||
val time: StateFlow<Long> = _time.asStateFlow()
|
||||
@@ -155,6 +155,10 @@ class TimerViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun onAction(action: TimerAction) {
|
||||
serviceHelper.startService(action)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
@@ -162,12 +166,13 @@ class TimerViewModel(
|
||||
val appPreferenceRepository = application.container.appPreferenceRepository
|
||||
val appStatRepository = application.container.appStatRepository
|
||||
val appTimerRepository = application.container.appTimerRepository
|
||||
val serviceHelper = application.container.serviceHelper
|
||||
val timerState = application.container.timerState
|
||||
val time = application.container.time
|
||||
|
||||
TimerViewModel(
|
||||
application = application,
|
||||
preferenceRepository = appPreferenceRepository,
|
||||
serviceHelper = serviceHelper,
|
||||
statRepository = appStatRepository,
|
||||
timerRepository = appTimerRepository,
|
||||
_timerState = timerState,
|
||||
|
||||
@@ -93,4 +93,5 @@
|
||||
<string name="bmc">BuyMeACoffee</string>
|
||||
<string name="selected">Selected</string>
|
||||
<string name="help_with_translation">Help with translation</string>
|
||||
<string name="timer_settings_reset_info">Reset the timer to change settings</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user