fix: Fix a bug that caused the app state to reset
When closing the app with the timer still running
This commit is contained in:
@@ -31,6 +31,8 @@ interface TimerRepository {
|
||||
var colorScheme: ColorScheme
|
||||
|
||||
var alarmSoundUri: Uri?
|
||||
|
||||
var serviceRunning: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,4 +49,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
|
||||
}
|
||||
@@ -77,9 +77,21 @@ class TimerService : Service() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
timerRepository.serviceRunning = true
|
||||
alarm = MediaPlayer.create(this, timerRepository.alarmSoundUri)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
timerRepository.serviceRunning = false
|
||||
runBlocking {
|
||||
job.cancel()
|
||||
saveTimeToDb()
|
||||
notificationManager.cancel(1)
|
||||
alarm?.release()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
when (intent?.action) {
|
||||
Actions.TOGGLE.toString() -> {
|
||||
@@ -283,6 +295,7 @@ class TimerService : Service() {
|
||||
private fun skipTimer(fromButton: Boolean = false) {
|
||||
skipScope.launch {
|
||||
saveTimeToDb()
|
||||
updateProgressSegments()
|
||||
showTimerNotification(0, paused = true, complete = !fromButton)
|
||||
startTime = 0L
|
||||
pauseTime = 0L
|
||||
@@ -388,16 +401,6 @@ class TimerService : Service() {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
runBlocking {
|
||||
job.cancel()
|
||||
saveTimeToDb()
|
||||
notificationManager.cancel(1)
|
||||
alarm?.release()
|
||||
}
|
||||
}
|
||||
|
||||
enum class Actions {
|
||||
TOGGLE, SKIP, RESET, STOP_ALARM, UPDATE_ALARM_TONE
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberSliderState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -95,6 +96,12 @@ fun SettingsScreenRoot(
|
||||
viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory)
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
viewModel.runTextFieldFlowCollection()
|
||||
onDispose { viewModel.cancelTextFieldFlowCollection() }
|
||||
}
|
||||
|
||||
val focusTimeInputFieldState = rememberSaveable(saver = TextFieldState.Saver) {
|
||||
viewModel.focusTimeTextFieldState
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
@@ -39,12 +40,15 @@ class SettingsViewModel(
|
||||
private val _preferencesState = MutableStateFlow(PreferencesState())
|
||||
val preferencesState = _preferencesState.asStateFlow()
|
||||
|
||||
val focusTimeTextFieldState =
|
||||
val focusTimeTextFieldState by lazy {
|
||||
TextFieldState((timerRepository.focusTime / 60000).toString())
|
||||
val shortBreakTimeTextFieldState =
|
||||
}
|
||||
val shortBreakTimeTextFieldState by lazy {
|
||||
TextFieldState((timerRepository.shortBreakTime / 60000).toString())
|
||||
val longBreakTimeTextFieldState =
|
||||
}
|
||||
val longBreakTimeTextFieldState by lazy {
|
||||
TextFieldState((timerRepository.longBreakTime / 60000).toString())
|
||||
}
|
||||
|
||||
val sessionsSliderState = SliderState(
|
||||
value = timerRepository.sessionLength.toFloat(),
|
||||
@@ -55,6 +59,8 @@ class SettingsViewModel(
|
||||
|
||||
val currentAlarmSound = timerRepository.alarmSoundUri.toString()
|
||||
|
||||
private val flowCollectionJob = SupervisorJob()
|
||||
|
||||
val alarmSound =
|
||||
preferenceRepository.getStringPreferenceFlow("alarm_sound").distinctUntilChanged()
|
||||
val alarmEnabled =
|
||||
@@ -79,7 +85,19 @@ class SettingsViewModel(
|
||||
)
|
||||
}
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
}
|
||||
|
||||
private fun updateSessionLength() {
|
||||
viewModelScope.launch {
|
||||
timerRepository.sessionLength = preferenceRepository.saveIntPreference(
|
||||
"session_length",
|
||||
sessionsSliderState.value.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun runTextFieldFlowCollection() {
|
||||
viewModelScope.launch(flowCollectionJob + Dispatchers.IO) {
|
||||
snapshotFlow { focusTimeTextFieldState.text }
|
||||
.debounce(500)
|
||||
.collect {
|
||||
@@ -91,7 +109,7 @@ class SettingsViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
viewModelScope.launch(flowCollectionJob + Dispatchers.IO) {
|
||||
snapshotFlow { shortBreakTimeTextFieldState.text }
|
||||
.debounce(500)
|
||||
.collect {
|
||||
@@ -103,7 +121,7 @@ class SettingsViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
viewModelScope.launch(flowCollectionJob + Dispatchers.IO) {
|
||||
snapshotFlow { longBreakTimeTextFieldState.text }
|
||||
.debounce(500)
|
||||
.collect {
|
||||
@@ -117,14 +135,7 @@ class SettingsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSessionLength() {
|
||||
viewModelScope.launch {
|
||||
timerRepository.sessionLength = preferenceRepository.saveIntPreference(
|
||||
"session_length",
|
||||
sessionsSliderState.value.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
fun cancelTextFieldFlowCollection() = flowCollectionJob.cancel()
|
||||
|
||||
fun saveAlarmEnabled(enabled: Boolean) {
|
||||
viewModelScope.launch {
|
||||
|
||||
@@ -52,98 +52,83 @@ class TimerViewModel(
|
||||
private var pauseDuration = 0L
|
||||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
timerRepository.focusTime =
|
||||
preferenceRepository.getIntPreference("focus_time")?.toLong()
|
||||
?: preferenceRepository.saveIntPreference(
|
||||
"focus_time",
|
||||
timerRepository.focusTime.toInt()
|
||||
).toLong()
|
||||
timerRepository.shortBreakTime =
|
||||
preferenceRepository.getIntPreference("short_break_time")?.toLong()
|
||||
?: preferenceRepository.saveIntPreference(
|
||||
"short_break_time",
|
||||
timerRepository.shortBreakTime.toInt()
|
||||
).toLong()
|
||||
timerRepository.longBreakTime =
|
||||
preferenceRepository.getIntPreference("long_break_time")?.toLong()
|
||||
?: preferenceRepository.saveIntPreference(
|
||||
"long_break_time",
|
||||
timerRepository.longBreakTime.toInt()
|
||||
).toLong()
|
||||
timerRepository.sessionLength = preferenceRepository.getIntPreference("session_length")
|
||||
?: preferenceRepository.saveIntPreference(
|
||||
"session_length",
|
||||
timerRepository.sessionLength
|
||||
)
|
||||
|
||||
timerRepository.alarmEnabled =
|
||||
preferenceRepository.getBooleanPreference("alarm_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("alarm_enabled", true)
|
||||
timerRepository.vibrateEnabled =
|
||||
preferenceRepository.getBooleanPreference("vibrate_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("vibrate_enabled", true)
|
||||
|
||||
timerRepository.alarmSoundUri = (
|
||||
preferenceRepository.getStringPreference("alarm_sound")
|
||||
?: preferenceRepository.saveStringPreference(
|
||||
"alarm_sound",
|
||||
(Settings.System.DEFAULT_ALARM_ALERT_URI
|
||||
?: Settings.System.DEFAULT_RINGTONE_URI).toString()
|
||||
if (!timerRepository.serviceRunning)
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
timerRepository.focusTime =
|
||||
preferenceRepository.getIntPreference("focus_time")?.toLong()
|
||||
?: preferenceRepository.saveIntPreference(
|
||||
"focus_time",
|
||||
timerRepository.focusTime.toInt()
|
||||
).toLong()
|
||||
timerRepository.shortBreakTime =
|
||||
preferenceRepository.getIntPreference("short_break_time")?.toLong()
|
||||
?: preferenceRepository.saveIntPreference(
|
||||
"short_break_time",
|
||||
timerRepository.shortBreakTime.toInt()
|
||||
).toLong()
|
||||
timerRepository.longBreakTime =
|
||||
preferenceRepository.getIntPreference("long_break_time")?.toLong()
|
||||
?: preferenceRepository.saveIntPreference(
|
||||
"long_break_time",
|
||||
timerRepository.longBreakTime.toInt()
|
||||
).toLong()
|
||||
timerRepository.sessionLength =
|
||||
preferenceRepository.getIntPreference("session_length")
|
||||
?: preferenceRepository.saveIntPreference(
|
||||
"session_length",
|
||||
timerRepository.sessionLength
|
||||
)
|
||||
).toUri()
|
||||
|
||||
resetTimer()
|
||||
timerRepository.alarmEnabled =
|
||||
preferenceRepository.getBooleanPreference("alarm_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("alarm_enabled", true)
|
||||
timerRepository.vibrateEnabled =
|
||||
preferenceRepository.getBooleanPreference("vibrate_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("vibrate_enabled", true)
|
||||
|
||||
var lastDate = statRepository.getLastDate()
|
||||
val today = LocalDate.now()
|
||||
timerRepository.alarmSoundUri = (
|
||||
preferenceRepository.getStringPreference("alarm_sound")
|
||||
?: preferenceRepository.saveStringPreference(
|
||||
"alarm_sound",
|
||||
(Settings.System.DEFAULT_ALARM_ALERT_URI
|
||||
?: Settings.System.DEFAULT_RINGTONE_URI).toString()
|
||||
)
|
||||
).toUri()
|
||||
|
||||
// Fills dates between today and lastDate with 0s to ensure continuous history
|
||||
if (lastDate != null)
|
||||
while (ChronoUnit.DAYS.between(lastDate, today) > 0) {
|
||||
lastDate = lastDate?.plusDays(1)
|
||||
statRepository.insertStat(Stat(lastDate!!, 0, 0, 0, 0, 0))
|
||||
_time.update { timerRepository.focusTime }
|
||||
cycles = 0
|
||||
startTime = 0L
|
||||
pauseTime = 0L
|
||||
pauseDuration = 0L
|
||||
|
||||
_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
|
||||
)
|
||||
}
|
||||
|
||||
delay(1500)
|
||||
var lastDate = statRepository.getLastDate()
|
||||
val today = LocalDate.now()
|
||||
|
||||
_timerState.update { currentState ->
|
||||
currentState.copy(showBrandTitle = false)
|
||||
// Fills dates between today and lastDate with 0s to ensure continuous history
|
||||
if (lastDate != null)
|
||||
while (ChronoUnit.DAYS.between(lastDate, today) > 0) {
|
||||
lastDate = lastDate?.plusDays(1)
|
||||
statRepository.insertStat(Stat(lastDate!!, 0, 0, 0, 0, 0))
|
||||
}
|
||||
|
||||
delay(1500)
|
||||
|
||||
_timerState.update { currentState ->
|
||||
currentState.copy(showBrandTitle = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetTimer() {
|
||||
viewModelScope.launch {
|
||||
saveTimeToDb()
|
||||
_time.update { timerRepository.focusTime }
|
||||
cycles = 0
|
||||
startTime = 0L
|
||||
pauseTime = 0L
|
||||
pauseDuration = 0L
|
||||
|
||||
_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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveTimeToDb() {
|
||||
when (timerState.value.timerMode) {
|
||||
TimerMode.FOCUS -> statRepository
|
||||
.addFocusTime((timerState.value.totalTime - time.value).coerceAtLeast(0L))
|
||||
|
||||
else -> statRepository
|
||||
.addBreakTime((timerState.value.totalTime - time.value).coerceAtLeast(0L))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
Reference in New Issue
Block a user