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