diff --git a/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt b/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt index e1e3068..a7a7b46 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt @@ -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 } \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt b/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt index bd69f62..6e6d33c 100644 --- a/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt +++ b/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt @@ -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 } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt index 12bcdac..b558816 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt @@ -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 } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt index a341d5f..ba1cf2c 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt @@ -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 { diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt index 5b88b45..e01a1d5 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt @@ -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 {