refactor(architecture): reorganize state variables into a separate state layer
This change also ensures a single source of truth for states used by the UI and those used by the Service. #117
This commit is contained in:
@@ -69,14 +69,14 @@ class MainActivity : ComponentActivity() {
|
||||
) {
|
||||
val colorScheme = colorScheme
|
||||
LaunchedEffect(colorScheme) {
|
||||
appContainer.appTimerRepository.colorScheme = colorScheme
|
||||
appContainer.stateRepository.colorScheme = colorScheme
|
||||
}
|
||||
|
||||
AppScreen(
|
||||
isPlus = isPlus,
|
||||
isAODEnabled = settingsState.aodEnabled,
|
||||
setTimerFrequency = {
|
||||
appContainer.appTimerRepository.timerFrequency = it
|
||||
appContainer.stateRepository.timerFrequency = it
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -86,13 +86,13 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
// Reduce the timer loop frequency when not visible to save battery power
|
||||
appContainer.appTimerRepository.timerFrequency = 1f
|
||||
// Reduce the timer loop frequency when not visible to save battery
|
||||
appContainer.stateRepository.timerFrequency = 1f
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
// Increase the timer loop frequency again when visible to make the progress smoother
|
||||
appContainer.appTimerRepository.timerFrequency = 60f
|
||||
appContainer.stateRepository.timerFrequency = 60f
|
||||
}
|
||||
}
|
||||
@@ -31,19 +31,16 @@ 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
|
||||
|
||||
interface AppContainer {
|
||||
val appPreferenceRepository: AppPreferenceRepository
|
||||
val appStatRepository: AppStatRepository
|
||||
val appTimerRepository: AppTimerRepository
|
||||
val stateRepository: StateRepository
|
||||
val billingManager: BillingManager
|
||||
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
|
||||
}
|
||||
@@ -58,7 +55,9 @@ class DefaultAppContainer(context: Context) : AppContainer {
|
||||
AppStatRepository(AppDatabase.getDatabase(context).statDao())
|
||||
}
|
||||
|
||||
override val appTimerRepository: AppTimerRepository by lazy { AppTimerRepository() }
|
||||
override val stateRepository: StateRepository by lazy {
|
||||
StateRepository()
|
||||
}
|
||||
|
||||
override val billingManager: BillingManager by lazy { BillingManagerProvider.manager }
|
||||
|
||||
@@ -93,20 +92,9 @@ class DefaultAppContainer(context: Context) : AppContainer {
|
||||
ServiceHelper(context)
|
||||
}
|
||||
|
||||
override val timerState: MutableStateFlow<TimerState> by lazy {
|
||||
MutableStateFlow(
|
||||
TimerState(
|
||||
totalTime = appTimerRepository.focusTime,
|
||||
timeStr = millisecondsToStr(appTimerRepository.focusTime),
|
||||
nextTimeStr = millisecondsToStr(appTimerRepository.shortBreakTime)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override val time: MutableStateFlow<Long> by lazy {
|
||||
MutableStateFlow(appTimerRepository.focusTime)
|
||||
MutableStateFlow(stateRepository.settingsState.value.focusTime)
|
||||
}
|
||||
|
||||
override var activityTurnScreenOn: (Boolean) -> Unit = {}
|
||||
|
||||
}
|
||||
31
app/src/main/java/org/nsh07/pomodoro/data/StateRepository.kt
Normal file
31
app/src/main/java/org/nsh07/pomodoro/data/StateRepository.kt
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.data
|
||||
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsState
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
|
||||
|
||||
class StateRepository {
|
||||
val timerState = MutableStateFlow(TimerState())
|
||||
val settingsState = MutableStateFlow(SettingsState())
|
||||
var timerFrequency: Float = 60f
|
||||
var colorScheme: ColorScheme = lightColorScheme()
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* 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.data
|
||||
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
/**
|
||||
* Interface that holds the timer durations for each timer type. This repository maintains a single
|
||||
* source of truth for the timer durations for the various ViewModels in the app.
|
||||
*/
|
||||
interface TimerRepository {
|
||||
var focusTime: Long
|
||||
var shortBreakTime: Long
|
||||
var longBreakTime: Long
|
||||
|
||||
var sessionLength: Int
|
||||
|
||||
var timerFrequency: Float
|
||||
|
||||
var alarmEnabled: Boolean
|
||||
var vibrateEnabled: Boolean
|
||||
var dndEnabled: Boolean
|
||||
|
||||
var colorScheme: ColorScheme
|
||||
|
||||
var alarmSoundUri: Uri?
|
||||
|
||||
var serviceRunning: MutableStateFlow<Boolean>
|
||||
}
|
||||
|
||||
/**
|
||||
* See [TimerRepository] for more details
|
||||
*/
|
||||
class AppTimerRepository : TimerRepository {
|
||||
override var focusTime = 25 * 60 * 1000L
|
||||
override var shortBreakTime = 5 * 60 * 1000L
|
||||
override var longBreakTime = 15 * 60 * 1000L
|
||||
override var sessionLength = 4
|
||||
override var timerFrequency: Float = 60f
|
||||
override var alarmEnabled = true
|
||||
override var vibrateEnabled = true
|
||||
override var dndEnabled: Boolean = false
|
||||
override var colorScheme = lightColorScheme()
|
||||
override var alarmSoundUri: Uri? =
|
||||
Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI
|
||||
override var serviceRunning = MutableStateFlow(false)
|
||||
}
|
||||
@@ -36,7 +36,6 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -53,12 +52,13 @@ class TimerService : Service() {
|
||||
(application as TomatoApplication).container
|
||||
}
|
||||
|
||||
private val timerRepository by lazy { appContainer.appTimerRepository }
|
||||
private val stateRepository by lazy { appContainer.stateRepository }
|
||||
private val statRepository by lazy { appContainer.appStatRepository }
|
||||
private val notificationManager by lazy { appContainer.notificationManager }
|
||||
private val notificationManagerService by lazy { appContainer.notificationManagerService }
|
||||
private val notificationBuilder by lazy { appContainer.notificationBuilder }
|
||||
private val _timerState by lazy { appContainer.timerState }
|
||||
private val _timerState by lazy { stateRepository.timerState }
|
||||
private val _settingsState by lazy { stateRepository.settingsState }
|
||||
private val _time by lazy { appContainer.time }
|
||||
|
||||
/**
|
||||
@@ -68,8 +68,6 @@ class TimerService : Service() {
|
||||
get() = _time.value
|
||||
set(value) = _time.update { value }
|
||||
|
||||
private val timerState by lazy { _timerState.asStateFlow() }
|
||||
|
||||
private var cycles = 0
|
||||
private var startTime = 0L
|
||||
private var pauseTime = 0L
|
||||
@@ -94,7 +92,7 @@ class TimerService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
private val cs by lazy { timerRepository.colorScheme }
|
||||
private val cs by lazy { stateRepository.colorScheme }
|
||||
|
||||
private lateinit var notificationStyle: NotificationCompat.ProgressStyle
|
||||
|
||||
@@ -104,12 +102,12 @@ class TimerService : Service() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
timerRepository.serviceRunning.update { true }
|
||||
stateRepository.timerState.update { it.copy(serviceRunning = true) }
|
||||
alarm = initializeMediaPlayer()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
timerRepository.serviceRunning.update { false }
|
||||
stateRepository.timerState.update { it.copy(serviceRunning = false) }
|
||||
runBlocking {
|
||||
job.cancel()
|
||||
saveTimeToDb()
|
||||
@@ -129,7 +127,7 @@ class TimerService : Service() {
|
||||
}
|
||||
|
||||
Actions.RESET.toString() -> {
|
||||
if (timerState.value.timerRunning) toggleTimer()
|
||||
if (_timerState.value.timerRunning) toggleTimer()
|
||||
skipScope.launch {
|
||||
resetTimer()
|
||||
stopForegroundService()
|
||||
@@ -148,7 +146,7 @@ class TimerService : Service() {
|
||||
private fun toggleTimer() {
|
||||
updateProgressSegments()
|
||||
|
||||
if (timerState.value.timerRunning) {
|
||||
if (_timerState.value.timerRunning) {
|
||||
setDoNotDisturb(false)
|
||||
notificationBuilder.clearActions().addTimerActions(
|
||||
this, R.drawable.play, getString(R.string.start)
|
||||
@@ -159,7 +157,7 @@ class TimerService : Service() {
|
||||
}
|
||||
pauseTime = SystemClock.elapsedRealtime()
|
||||
} else {
|
||||
if (timerState.value.timerMode == TimerMode.FOCUS) setDoNotDisturb(true)
|
||||
if (_timerState.value.timerMode == TimerMode.FOCUS) setDoNotDisturb(true)
|
||||
else setDoNotDisturb(false)
|
||||
notificationBuilder.clearActions().addTimerActions(
|
||||
this, R.drawable.pause, getString(R.string.stop)
|
||||
@@ -171,19 +169,20 @@ class TimerService : Service() {
|
||||
|
||||
timerScope.launch {
|
||||
while (true) {
|
||||
if (!timerState.value.timerRunning) break
|
||||
if (!_timerState.value.timerRunning) break
|
||||
if (startTime == 0L) startTime = SystemClock.elapsedRealtime()
|
||||
|
||||
time = when (timerState.value.timerMode) {
|
||||
TimerMode.FOCUS -> timerRepository.focusTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration)
|
||||
val settingsState = _settingsState.value
|
||||
time = when (_timerState.value.timerMode) {
|
||||
TimerMode.FOCUS -> settingsState.focusTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration)
|
||||
|
||||
TimerMode.SHORT_BREAK -> timerRepository.shortBreakTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration)
|
||||
TimerMode.SHORT_BREAK -> settingsState.shortBreakTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration)
|
||||
|
||||
else -> timerRepository.longBreakTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration)
|
||||
else -> settingsState.longBreakTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration)
|
||||
}
|
||||
|
||||
iterations =
|
||||
(iterations + 1) % timerRepository.timerFrequency.toInt().coerceAtLeast(1)
|
||||
(iterations + 1) % stateRepository.timerFrequency.toInt().coerceAtLeast(1)
|
||||
|
||||
if (iterations == 0) showTimerNotification(time.toInt())
|
||||
|
||||
@@ -199,7 +198,7 @@ class TimerService : Service() {
|
||||
timeStr = millisecondsToStr(time)
|
||||
)
|
||||
}
|
||||
val totalTime = timerState.value.totalTime
|
||||
val totalTime = _timerState.value.totalTime
|
||||
|
||||
if (totalTime - time < lastSavedDuration)
|
||||
lastSavedDuration =
|
||||
@@ -208,7 +207,7 @@ class TimerService : Service() {
|
||||
saveTimeToDb()
|
||||
}
|
||||
|
||||
delay((1000f / timerRepository.timerFrequency).toLong())
|
||||
delay((1000f / stateRepository.timerFrequency).toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,21 +220,23 @@ class TimerService : Service() {
|
||||
fun showTimerNotification(
|
||||
remainingTime: Int, paused: Boolean = false, complete: Boolean = false
|
||||
) {
|
||||
val settingsState = _settingsState.value
|
||||
|
||||
if (complete) notificationBuilder.clearActions().addStopAlarmAction(this)
|
||||
|
||||
val totalTime = when (timerState.value.timerMode) {
|
||||
TimerMode.FOCUS -> timerRepository.focusTime.toInt()
|
||||
TimerMode.SHORT_BREAK -> timerRepository.shortBreakTime.toInt()
|
||||
else -> timerRepository.longBreakTime.toInt()
|
||||
val totalTime = when (_timerState.value.timerMode) {
|
||||
TimerMode.FOCUS -> settingsState.focusTime.toInt()
|
||||
TimerMode.SHORT_BREAK -> settingsState.shortBreakTime.toInt()
|
||||
else -> settingsState.longBreakTime.toInt()
|
||||
}
|
||||
|
||||
val currentTimer = when (timerState.value.timerMode) {
|
||||
val currentTimer = when (_timerState.value.timerMode) {
|
||||
TimerMode.FOCUS -> getString(R.string.focus)
|
||||
TimerMode.SHORT_BREAK -> getString(R.string.short_break)
|
||||
else -> getString(R.string.long_break)
|
||||
}
|
||||
|
||||
val nextTimer = when (timerState.value.nextTimerMode) {
|
||||
val nextTimer = when (_timerState.value.nextTimerMode) {
|
||||
TimerMode.FOCUS -> getString(R.string.focus)
|
||||
TimerMode.SHORT_BREAK -> getString(R.string.short_break)
|
||||
else -> getString(R.string.long_break)
|
||||
@@ -258,14 +259,14 @@ class TimerService : Service() {
|
||||
getString(
|
||||
R.string.up_next_notification,
|
||||
nextTimer,
|
||||
timerState.value.nextTimeStr
|
||||
_timerState.value.nextTimeStr
|
||||
)
|
||||
)
|
||||
.setStyle(
|
||||
notificationStyle
|
||||
.setProgress( // Set the current progress by filling the previous intervals and part of the current interval
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
|
||||
(totalTime - remainingTime) + ((cycles + 1) / 2) * timerRepository.focusTime.toInt() + (cycles / 2) * timerRepository.shortBreakTime.toInt()
|
||||
(totalTime - remainingTime) + ((cycles + 1) / 2) * settingsState.focusTime.toInt() + (cycles / 2) * settingsState.shortBreakTime.toInt()
|
||||
} else (totalTime - remainingTime)
|
||||
)
|
||||
)
|
||||
@@ -283,37 +284,38 @@ class TimerService : Service() {
|
||||
}
|
||||
|
||||
private fun updateProgressSegments() {
|
||||
val settingsState = _settingsState.value
|
||||
notificationStyle = NotificationCompat.ProgressStyle()
|
||||
.also {
|
||||
// Add all the Focus, Short break and long break intervals in order
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
|
||||
// Android 16 and later supports live updates
|
||||
// Set progress bar sections if on Baklava or later
|
||||
for (i in 0..<timerRepository.sessionLength * 2) {
|
||||
for (i in 0..<settingsState.sessionLength * 2) {
|
||||
if (i % 2 == 0) it.addProgressSegment(
|
||||
NotificationCompat.ProgressStyle.Segment(
|
||||
timerRepository.focusTime.toInt()
|
||||
settingsState.focusTime.toInt()
|
||||
)
|
||||
.setColor(cs.primary.toArgb())
|
||||
)
|
||||
else if (i != (timerRepository.sessionLength * 2 - 1)) it.addProgressSegment(
|
||||
else if (i != (settingsState.sessionLength * 2 - 1)) it.addProgressSegment(
|
||||
NotificationCompat.ProgressStyle.Segment(
|
||||
timerRepository.shortBreakTime.toInt()
|
||||
settingsState.shortBreakTime.toInt()
|
||||
).setColor(cs.tertiary.toArgb())
|
||||
)
|
||||
else it.addProgressSegment(
|
||||
NotificationCompat.ProgressStyle.Segment(
|
||||
timerRepository.longBreakTime.toInt()
|
||||
settingsState.longBreakTime.toInt()
|
||||
).setColor(cs.tertiary.toArgb())
|
||||
)
|
||||
}
|
||||
} else {
|
||||
it.addProgressSegment(
|
||||
NotificationCompat.ProgressStyle.Segment(
|
||||
when (timerState.value.timerMode) {
|
||||
TimerMode.FOCUS -> timerRepository.focusTime.toInt()
|
||||
TimerMode.SHORT_BREAK -> timerRepository.shortBreakTime.toInt()
|
||||
else -> timerRepository.longBreakTime.toInt()
|
||||
when (_timerState.value.timerMode) {
|
||||
TimerMode.FOCUS -> settingsState.focusTime.toInt()
|
||||
TimerMode.SHORT_BREAK -> settingsState.shortBreakTime.toInt()
|
||||
else -> settingsState.longBreakTime.toInt()
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -322,10 +324,12 @@ class TimerService : Service() {
|
||||
}
|
||||
|
||||
private suspend fun resetTimer() {
|
||||
val settingsState = _settingsState.value
|
||||
|
||||
updateProgressSegments()
|
||||
saveTimeToDb()
|
||||
lastSavedDuration = 0
|
||||
time = timerRepository.focusTime
|
||||
time = settingsState.focusTime
|
||||
cycles = 0
|
||||
startTime = 0L
|
||||
pauseTime = 0L
|
||||
@@ -336,15 +340,16 @@ class TimerService : Service() {
|
||||
timerMode = TimerMode.FOCUS,
|
||||
timeStr = millisecondsToStr(time),
|
||||
totalTime = time,
|
||||
nextTimerMode = if (timerRepository.sessionLength > 1) TimerMode.SHORT_BREAK else TimerMode.LONG_BREAK,
|
||||
nextTimeStr = millisecondsToStr(if (timerRepository.sessionLength > 1) timerRepository.shortBreakTime else timerRepository.longBreakTime),
|
||||
nextTimerMode = if (settingsState.sessionLength > 1) TimerMode.SHORT_BREAK else TimerMode.LONG_BREAK,
|
||||
nextTimeStr = millisecondsToStr(if (settingsState.sessionLength > 1) settingsState.shortBreakTime else settingsState.longBreakTime),
|
||||
currentFocusCount = 1,
|
||||
totalFocusCount = timerRepository.sessionLength
|
||||
totalFocusCount = settingsState.sessionLength
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun skipTimer(fromButton: Boolean = false) {
|
||||
val settingsState = _settingsState.value
|
||||
updateProgressSegments()
|
||||
saveTimeToDb()
|
||||
updateProgressSegments()
|
||||
@@ -354,30 +359,30 @@ class TimerService : Service() {
|
||||
pauseTime = 0L
|
||||
pauseDuration = 0L
|
||||
|
||||
cycles = (cycles + 1) % (timerRepository.sessionLength * 2)
|
||||
cycles = (cycles + 1) % (settingsState.sessionLength * 2)
|
||||
|
||||
if (cycles % 2 == 0) {
|
||||
if (timerState.value.timerRunning) setDoNotDisturb(true)
|
||||
time = timerRepository.focusTime
|
||||
if (_timerState.value.timerRunning) setDoNotDisturb(true)
|
||||
time = settingsState.focusTime
|
||||
_timerState.update { currentState ->
|
||||
currentState.copy(
|
||||
timerMode = TimerMode.FOCUS,
|
||||
timeStr = millisecondsToStr(time),
|
||||
totalTime = time,
|
||||
nextTimerMode = if (cycles == (timerRepository.sessionLength - 1) * 2) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK,
|
||||
nextTimeStr = if (cycles == (timerRepository.sessionLength - 1) * 2) millisecondsToStr(
|
||||
timerRepository.longBreakTime
|
||||
nextTimerMode = if (cycles == (settingsState.sessionLength - 1) * 2) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK,
|
||||
nextTimeStr = if (cycles == (settingsState.sessionLength - 1) * 2) millisecondsToStr(
|
||||
settingsState.longBreakTime
|
||||
) else millisecondsToStr(
|
||||
timerRepository.shortBreakTime
|
||||
settingsState.shortBreakTime
|
||||
),
|
||||
currentFocusCount = cycles / 2 + 1,
|
||||
totalFocusCount = timerRepository.sessionLength
|
||||
totalFocusCount = settingsState.sessionLength
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (timerState.value.timerRunning) setDoNotDisturb(false)
|
||||
val long = cycles == (timerRepository.sessionLength * 2) - 1
|
||||
time = if (long) timerRepository.longBreakTime else timerRepository.shortBreakTime
|
||||
if (_timerState.value.timerRunning) setDoNotDisturb(false)
|
||||
val long = cycles == (settingsState.sessionLength * 2) - 1
|
||||
time = if (long) settingsState.longBreakTime else settingsState.shortBreakTime
|
||||
|
||||
_timerState.update { currentState ->
|
||||
currentState.copy(
|
||||
@@ -385,14 +390,15 @@ class TimerService : Service() {
|
||||
timeStr = millisecondsToStr(time),
|
||||
totalTime = time,
|
||||
nextTimerMode = TimerMode.FOCUS,
|
||||
nextTimeStr = millisecondsToStr(timerRepository.focusTime)
|
||||
nextTimeStr = millisecondsToStr(settingsState.focusTime)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startAlarm() {
|
||||
if (timerRepository.alarmEnabled) alarm?.start()
|
||||
val settingsState = _settingsState.value
|
||||
if (settingsState.alarmEnabled) alarm?.start()
|
||||
|
||||
appContainer.activityTurnScreenOn(true)
|
||||
|
||||
@@ -401,7 +407,7 @@ class TimerService : Service() {
|
||||
stopAlarm()
|
||||
}
|
||||
|
||||
if (timerRepository.vibrateEnabled) {
|
||||
if (settingsState.vibrateEnabled) {
|
||||
if (!vibrator.hasVibrator()) {
|
||||
return
|
||||
}
|
||||
@@ -413,14 +419,15 @@ class TimerService : Service() {
|
||||
}
|
||||
|
||||
fun stopAlarm() {
|
||||
val settingsState = _settingsState.value
|
||||
autoAlarmStopScope?.cancel()
|
||||
|
||||
if (timerRepository.alarmEnabled) {
|
||||
if (settingsState.alarmEnabled) {
|
||||
alarm?.pause()
|
||||
alarm?.seekTo(0)
|
||||
}
|
||||
|
||||
if (timerRepository.vibrateEnabled) {
|
||||
if (settingsState.vibrateEnabled) {
|
||||
vibrator.cancel()
|
||||
}
|
||||
|
||||
@@ -434,10 +441,10 @@ class TimerService : Service() {
|
||||
getString(R.string.start_next)
|
||||
)
|
||||
showTimerNotification(
|
||||
when (timerState.value.timerMode) {
|
||||
TimerMode.FOCUS -> timerRepository.focusTime.toInt()
|
||||
TimerMode.SHORT_BREAK -> timerRepository.shortBreakTime.toInt()
|
||||
else -> timerRepository.longBreakTime.toInt()
|
||||
when (_timerState.value.timerMode) {
|
||||
TimerMode.FOCUS -> settingsState.focusTime.toInt()
|
||||
TimerMode.SHORT_BREAK -> settingsState.shortBreakTime.toInt()
|
||||
else -> settingsState.longBreakTime.toInt()
|
||||
}, paused = true, complete = false
|
||||
)
|
||||
}
|
||||
@@ -451,7 +458,7 @@ class TimerService : Service() {
|
||||
.setUsage(AudioAttributes.USAGE_ALARM)
|
||||
.build()
|
||||
)
|
||||
timerRepository.alarmSoundUri?.let {
|
||||
_settingsState.value.alarmSoundUri?.let {
|
||||
setDataSource(applicationContext, it)
|
||||
prepare()
|
||||
}
|
||||
@@ -463,7 +470,7 @@ class TimerService : Service() {
|
||||
}
|
||||
|
||||
private fun setDoNotDisturb(doNotDisturb: Boolean) {
|
||||
if (timerRepository.dndEnabled && notificationManagerService.isNotificationPolicyAccessGranted()) {
|
||||
if (_settingsState.value.dndEnabled && notificationManagerService.isNotificationPolicyAccessGranted()) {
|
||||
if (doNotDisturb) {
|
||||
notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS)
|
||||
} else notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL)
|
||||
@@ -477,8 +484,8 @@ class TimerService : Service() {
|
||||
|
||||
suspend fun saveTimeToDb() {
|
||||
saveLock.withLock {
|
||||
val elapsedTime = timerState.value.totalTime - time
|
||||
when (timerState.value.timerMode) {
|
||||
val elapsedTime = _timerState.value.totalTime - time
|
||||
when (_timerState.value.timerMode) {
|
||||
TimerMode.FOCUS -> statRepository.addFocusTime(
|
||||
(elapsedTime - lastSavedDuration).coerceAtLeast(0L)
|
||||
)
|
||||
|
||||
@@ -62,7 +62,6 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.nsh07.pomodoro.R
|
||||
@@ -92,10 +91,10 @@ fun AlarmSettings(
|
||||
|
||||
var alarmName by remember { mutableStateOf("...") }
|
||||
|
||||
LaunchedEffect(settingsState.alarmSound) {
|
||||
LaunchedEffect(settingsState.alarmSoundUri) {
|
||||
withContext(Dispatchers.IO) {
|
||||
alarmName =
|
||||
RingtoneManager.getRingtone(context, settingsState.alarmSound.toUri())
|
||||
RingtoneManager.getRingtone(context, settingsState.alarmSoundUri)
|
||||
?.getTitle(context) ?: ""
|
||||
}
|
||||
}
|
||||
@@ -119,11 +118,11 @@ fun AlarmSettings(
|
||||
}
|
||||
|
||||
@SuppressLint("LocalContextGetResourceValueCall")
|
||||
val ringtonePickerIntent = remember(settingsState.alarmSound) {
|
||||
val ringtonePickerIntent = remember(settingsState.alarmSoundUri) {
|
||||
Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
|
||||
putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM)
|
||||
putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, context.getString(R.string.alarm_sound))
|
||||
putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, settingsState.alarmSound.toUri())
|
||||
putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, settingsState.alarmSoundUri)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,17 +17,27 @@
|
||||
|
||||
package org.nsh07.pomodoro.ui.settingsScreen.viewModel
|
||||
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
@Immutable
|
||||
data class SettingsState(
|
||||
val theme: String = "auto",
|
||||
val alarmSound: String = "",
|
||||
val colorScheme: String = Color.White.toString(),
|
||||
val blackTheme: Boolean = false,
|
||||
val aodEnabled: Boolean = false,
|
||||
val alarmEnabled: Boolean = true,
|
||||
val vibrateEnabled: Boolean = true,
|
||||
val dndEnabled: Boolean = false
|
||||
val dndEnabled: Boolean = false,
|
||||
|
||||
val focusTime: Long = 25 * 60 * 1000L,
|
||||
val shortBreakTime: Long = 5 * 60 * 1000L,
|
||||
val longBreakTime: Long = 15 * 60 * 1000L,
|
||||
|
||||
val sessionLength: Int = 4,
|
||||
|
||||
val alarmSoundUri: Uri? =
|
||||
Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.compose.material3.SliderState
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
|
||||
@@ -35,51 +36,57 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
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.data.PreferenceRepository
|
||||
import org.nsh07.pomodoro.data.StateRepository
|
||||
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 preferenceRepository: PreferenceRepository,
|
||||
private val stateRepository: StateRepository,
|
||||
private val serviceHelper: ServiceHelper,
|
||||
private val time: MutableStateFlow<Long>,
|
||||
private val timerRepository: TimerRepository,
|
||||
private val timerState: MutableStateFlow<TimerState>
|
||||
private val time: MutableStateFlow<Long>
|
||||
) : ViewModel() {
|
||||
val backStack = mutableStateListOf<Screen.Settings>(Screen.Settings.Main)
|
||||
|
||||
val isPlus = billingManager.isPlus
|
||||
val serviceRunning = timerRepository.serviceRunning.asStateFlow()
|
||||
val serviceRunning = stateRepository.timerState.map { it.serviceRunning }
|
||||
.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5000),
|
||||
false
|
||||
)
|
||||
|
||||
private val _settingsState = MutableStateFlow(SettingsState())
|
||||
private val _settingsState = stateRepository.settingsState
|
||||
val settingsState = _settingsState.asStateFlow()
|
||||
|
||||
val focusTimeTextFieldState by lazy {
|
||||
TextFieldState((timerRepository.focusTime / 60000).toString())
|
||||
TextFieldState((_settingsState.value.focusTime / 60000).toString())
|
||||
}
|
||||
val shortBreakTimeTextFieldState by lazy {
|
||||
TextFieldState((timerRepository.shortBreakTime / 60000).toString())
|
||||
TextFieldState((_settingsState.value.shortBreakTime / 60000).toString())
|
||||
}
|
||||
val longBreakTimeTextFieldState by lazy {
|
||||
TextFieldState((timerRepository.longBreakTime / 60000).toString())
|
||||
TextFieldState((_settingsState.value.longBreakTime / 60000).toString())
|
||||
}
|
||||
|
||||
val sessionsSliderState by lazy {
|
||||
SliderState(
|
||||
value = timerRepository.sessionLength.toFloat(),
|
||||
value = _settingsState.value.sessionLength.toFloat(),
|
||||
steps = 4,
|
||||
valueRange = 1f..6f,
|
||||
onValueChangeFinished = ::updateSessionLength
|
||||
@@ -110,11 +117,15 @@ class SettingsViewModel(
|
||||
}
|
||||
|
||||
private fun updateSessionLength() {
|
||||
viewModelScope.launch {
|
||||
timerRepository.sessionLength = preferenceRepository.saveIntPreference(
|
||||
"session_length",
|
||||
sessionsSliderState.value.toInt()
|
||||
)
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_settingsState.update { currentState ->
|
||||
currentState.copy(
|
||||
sessionLength = preferenceRepository.saveIntPreference(
|
||||
"session_length",
|
||||
sessionsSliderState.value.toInt()
|
||||
)
|
||||
)
|
||||
}
|
||||
refreshTimer()
|
||||
}
|
||||
}
|
||||
@@ -125,11 +136,13 @@ class SettingsViewModel(
|
||||
.debounce(500)
|
||||
.collect {
|
||||
if (it.isNotEmpty()) {
|
||||
timerRepository.focusTime = it.toString().toLong() * 60 * 1000
|
||||
_settingsState.update { currentState ->
|
||||
currentState.copy(focusTime = it.toString().toLong() * 60 * 1000)
|
||||
}
|
||||
refreshTimer()
|
||||
preferenceRepository.saveIntPreference(
|
||||
"focus_time",
|
||||
timerRepository.focusTime.toInt()
|
||||
_settingsState.value.focusTime.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -139,11 +152,13 @@ class SettingsViewModel(
|
||||
.debounce(500)
|
||||
.collect {
|
||||
if (it.isNotEmpty()) {
|
||||
timerRepository.shortBreakTime = it.toString().toLong() * 60 * 1000
|
||||
_settingsState.update { currentState ->
|
||||
currentState.copy(shortBreakTime = it.toString().toLong() * 60 * 1000)
|
||||
}
|
||||
refreshTimer()
|
||||
preferenceRepository.saveIntPreference(
|
||||
"short_break_time",
|
||||
timerRepository.shortBreakTime.toInt()
|
||||
_settingsState.value.shortBreakTime.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -153,11 +168,13 @@ class SettingsViewModel(
|
||||
.debounce(500)
|
||||
.collect {
|
||||
if (it.isNotEmpty()) {
|
||||
timerRepository.longBreakTime = it.toString().toLong() * 60 * 1000
|
||||
_settingsState.update { currentState ->
|
||||
currentState.copy(longBreakTime = it.toString().toLong() * 60 * 1000)
|
||||
}
|
||||
refreshTimer()
|
||||
preferenceRepository.saveIntPreference(
|
||||
"long_break_time",
|
||||
timerRepository.longBreakTime.toInt()
|
||||
_settingsState.value.longBreakTime.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -173,7 +190,6 @@ class SettingsViewModel(
|
||||
|
||||
private fun saveAlarmEnabled(enabled: Boolean) {
|
||||
viewModelScope.launch {
|
||||
timerRepository.alarmEnabled = enabled
|
||||
_settingsState.update { currentState ->
|
||||
currentState.copy(alarmEnabled = enabled)
|
||||
}
|
||||
@@ -183,7 +199,6 @@ class SettingsViewModel(
|
||||
|
||||
private fun saveVibrateEnabled(enabled: Boolean) {
|
||||
viewModelScope.launch {
|
||||
timerRepository.vibrateEnabled = enabled
|
||||
_settingsState.update { currentState ->
|
||||
currentState.copy(vibrateEnabled = enabled)
|
||||
}
|
||||
@@ -193,7 +208,6 @@ class SettingsViewModel(
|
||||
|
||||
private fun saveDndEnabled(enabled: Boolean) {
|
||||
viewModelScope.launch {
|
||||
timerRepository.dndEnabled = enabled
|
||||
_settingsState.update { currentState ->
|
||||
currentState.copy(dndEnabled = enabled)
|
||||
}
|
||||
@@ -203,9 +217,8 @@ class SettingsViewModel(
|
||||
|
||||
private fun saveAlarmSound(uri: Uri?) {
|
||||
viewModelScope.launch {
|
||||
timerRepository.alarmSoundUri = uri
|
||||
_settingsState.update { currentState ->
|
||||
currentState.copy(alarmSound = uri.toString())
|
||||
currentState.copy(alarmSoundUri = uri)
|
||||
}
|
||||
preferenceRepository.saveStringPreference("alarm_sound", uri.toString())
|
||||
}
|
||||
@@ -248,32 +261,71 @@ class SettingsViewModel(
|
||||
}
|
||||
|
||||
suspend fun reloadSettings() {
|
||||
var settingsState = _settingsState.value
|
||||
val focusTime =
|
||||
preferenceRepository.getIntPreference("focus_time")?.toLong()
|
||||
?: preferenceRepository.saveIntPreference(
|
||||
"focus_time",
|
||||
settingsState.focusTime.toInt()
|
||||
).toLong()
|
||||
val shortBreakTime =
|
||||
preferenceRepository.getIntPreference("short_break_time")?.toLong()
|
||||
?: preferenceRepository.saveIntPreference(
|
||||
"short_break_time",
|
||||
settingsState.shortBreakTime.toInt()
|
||||
).toLong()
|
||||
val longBreakTime =
|
||||
preferenceRepository.getIntPreference("long_break_time")?.toLong()
|
||||
?: preferenceRepository.saveIntPreference(
|
||||
"long_break_time",
|
||||
settingsState.longBreakTime.toInt()
|
||||
).toLong()
|
||||
val sessionLength =
|
||||
preferenceRepository.getIntPreference("session_length")
|
||||
?: preferenceRepository.saveIntPreference(
|
||||
"session_length",
|
||||
settingsState.sessionLength
|
||||
)
|
||||
|
||||
val alarmSoundUri = (
|
||||
preferenceRepository.getStringPreference("alarm_sound")
|
||||
?: preferenceRepository.saveStringPreference(
|
||||
"alarm_sound",
|
||||
(Settings.System.DEFAULT_ALARM_ALERT_URI
|
||||
?: Settings.System.DEFAULT_RINGTONE_URI).toString()
|
||||
)
|
||||
).toUri()
|
||||
|
||||
val theme = preferenceRepository.getStringPreference("theme")
|
||||
?: preferenceRepository.saveStringPreference("theme", "auto")
|
||||
?: preferenceRepository.saveStringPreference("theme", settingsState.theme)
|
||||
val colorScheme = preferenceRepository.getStringPreference("color_scheme")
|
||||
?: preferenceRepository.saveStringPreference("color_scheme", Color.White.toString())
|
||||
?: preferenceRepository.saveStringPreference("color_scheme", settingsState.colorScheme)
|
||||
val blackTheme = preferenceRepository.getBooleanPreference("black_theme")
|
||||
?: preferenceRepository.saveBooleanPreference("black_theme", false)
|
||||
?: preferenceRepository.saveBooleanPreference("black_theme", settingsState.blackTheme)
|
||||
val aodEnabled = preferenceRepository.getBooleanPreference("aod_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("aod_enabled", false)
|
||||
val alarmSound = preferenceRepository.getStringPreference("alarm_sound")
|
||||
?: preferenceRepository.saveStringPreference(
|
||||
"alarm_sound",
|
||||
(Settings.System.DEFAULT_ALARM_ALERT_URI
|
||||
?: Settings.System.DEFAULT_RINGTONE_URI).toString()
|
||||
)
|
||||
?: preferenceRepository.saveBooleanPreference("aod_enabled", settingsState.aodEnabled)
|
||||
val alarmEnabled = preferenceRepository.getBooleanPreference("alarm_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("alarm_enabled", true)
|
||||
?: preferenceRepository.saveBooleanPreference(
|
||||
"alarm_enabled",
|
||||
settingsState.alarmEnabled
|
||||
)
|
||||
val vibrateEnabled = preferenceRepository.getBooleanPreference("vibrate_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("vibrate_enabled", true)
|
||||
?: preferenceRepository.saveBooleanPreference(
|
||||
"vibrate_enabled",
|
||||
settingsState.vibrateEnabled
|
||||
)
|
||||
val dndEnabled = preferenceRepository.getBooleanPreference("dnd_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("dnd_enabled", false)
|
||||
?: preferenceRepository.saveBooleanPreference("dnd_enabled", settingsState.dndEnabled)
|
||||
|
||||
_settingsState.update { currentState ->
|
||||
currentState.copy(
|
||||
focusTime = focusTime,
|
||||
shortBreakTime = shortBreakTime,
|
||||
longBreakTime = longBreakTime,
|
||||
sessionLength = sessionLength,
|
||||
theme = theme,
|
||||
colorScheme = colorScheme,
|
||||
alarmSound = alarmSound,
|
||||
alarmSoundUri = alarmSoundUri,
|
||||
blackTheme = blackTheme,
|
||||
aodEnabled = aodEnabled,
|
||||
alarmEnabled = alarmEnabled,
|
||||
@@ -281,21 +333,40 @@ class SettingsViewModel(
|
||||
dndEnabled = dndEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshTimer() {
|
||||
if (!serviceRunning.value) {
|
||||
time.update { timerRepository.focusTime }
|
||||
settingsState = _settingsState.value
|
||||
|
||||
timerState.update { currentState ->
|
||||
time.update { settingsState.focusTime }
|
||||
|
||||
if (!stateRepository.timerState.value.serviceRunning)
|
||||
stateRepository.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),
|
||||
nextTimerMode = if (settingsState.sessionLength > 1) TimerMode.SHORT_BREAK else TimerMode.LONG_BREAK,
|
||||
nextTimeStr = millisecondsToStr(if (settingsState.sessionLength > 1) settingsState.shortBreakTime else settingsState.longBreakTime),
|
||||
currentFocusCount = 1,
|
||||
totalFocusCount = timerRepository.sessionLength
|
||||
totalFocusCount = settingsState.sessionLength
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshTimer() {
|
||||
if (!serviceRunning.value) {
|
||||
val settingsState = _settingsState.value
|
||||
|
||||
time.update { settingsState.focusTime }
|
||||
|
||||
stateRepository.timerState.update { currentState ->
|
||||
currentState.copy(
|
||||
timerMode = TimerMode.FOCUS,
|
||||
timeStr = millisecondsToStr(time.value),
|
||||
totalTime = time.value,
|
||||
nextTimerMode = if (settingsState.sessionLength > 1) TimerMode.SHORT_BREAK else TimerMode.LONG_BREAK,
|
||||
nextTimeStr = millisecondsToStr(if (settingsState.sessionLength > 1) settingsState.shortBreakTime else settingsState.longBreakTime),
|
||||
currentFocusCount = 1,
|
||||
totalFocusCount = settingsState.sessionLength
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -307,18 +378,16 @@ class SettingsViewModel(
|
||||
val application = (this[APPLICATION_KEY] as TomatoApplication)
|
||||
val appBillingManager = application.container.billingManager
|
||||
val appPreferenceRepository = application.container.appPreferenceRepository
|
||||
val appTimerRepository = application.container.appTimerRepository
|
||||
val serviceHelper = application.container.serviceHelper
|
||||
val stateRepository = application.container.stateRepository
|
||||
val time = application.container.time
|
||||
val timerState = application.container.timerState
|
||||
|
||||
SettingsViewModel(
|
||||
billingManager = appBillingManager,
|
||||
preferenceRepository = appPreferenceRepository,
|
||||
serviceHelper = serviceHelper,
|
||||
time = time,
|
||||
timerRepository = appTimerRepository,
|
||||
timerState = timerState
|
||||
stateRepository = stateRepository,
|
||||
time = time
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
* 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.ui.timerScreen.viewModel
|
||||
@@ -17,7 +27,8 @@ data class TimerState(
|
||||
val showBrandTitle: Boolean = true,
|
||||
val currentFocusCount: Int = 1,
|
||||
val totalFocusCount: Int = 4,
|
||||
val alarmRinging: Boolean = false
|
||||
val alarmRinging: Boolean = false,
|
||||
val serviceRunning: Boolean = false
|
||||
)
|
||||
|
||||
enum class TimerMode {
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
package org.nsh07.pomodoro.ui.timerScreen.viewModel
|
||||
|
||||
import android.provider.Settings
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
|
||||
@@ -37,122 +35,49 @@ import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.nsh07.pomodoro.TomatoApplication
|
||||
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.data.StateRepository
|
||||
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(
|
||||
private val preferenceRepository: PreferenceRepository,
|
||||
private val serviceHelper: ServiceHelper,
|
||||
private val stateRepository: StateRepository,
|
||||
private val statRepository: StatRepository,
|
||||
private val timerRepository: TimerRepository,
|
||||
private val _timerState: MutableStateFlow<TimerState>,
|
||||
private val _time: MutableStateFlow<Long>
|
||||
) : ViewModel() {
|
||||
val timerState: StateFlow<TimerState> = _timerState.asStateFlow()
|
||||
val timerState: StateFlow<TimerState> = stateRepository.timerState.asStateFlow()
|
||||
|
||||
val time: StateFlow<Long> = _time.asStateFlow()
|
||||
|
||||
val progress = _time.combine(_timerState) { remainingTime, uiState ->
|
||||
val progress = _time.combine(stateRepository.timerState) { remainingTime, uiState ->
|
||||
(uiState.totalTime.toFloat() - remainingTime) / uiState.totalTime
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 0f)
|
||||
|
||||
private var cycles = 0
|
||||
|
||||
private var startTime = 0L
|
||||
private var pauseTime = 0L
|
||||
private var pauseDuration = 0L
|
||||
|
||||
init {
|
||||
if (!timerRepository.serviceRunning.value)
|
||||
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
|
||||
)
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
var lastDate = statRepository.getLastDate()
|
||||
val today = LocalDate.now()
|
||||
|
||||
timerRepository.alarmEnabled =
|
||||
preferenceRepository.getBooleanPreference("alarm_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("alarm_enabled", true)
|
||||
timerRepository.vibrateEnabled =
|
||||
preferenceRepository.getBooleanPreference("vibrate_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("vibrate_enabled", true)
|
||||
timerRepository.dndEnabled =
|
||||
preferenceRepository.getBooleanPreference("dnd_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("dnd_enabled", false)
|
||||
|
||||
timerRepository.alarmSoundUri = (
|
||||
preferenceRepository.getStringPreference("alarm_sound")
|
||||
?: preferenceRepository.saveStringPreference(
|
||||
"alarm_sound",
|
||||
(Settings.System.DEFAULT_ALARM_ALERT_URI
|
||||
?: Settings.System.DEFAULT_RINGTONE_URI).toString()
|
||||
)
|
||||
).toUri()
|
||||
|
||||
_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
|
||||
)
|
||||
}
|
||||
|
||||
var lastDate = statRepository.getLastDate()
|
||||
val today = LocalDate.now()
|
||||
|
||||
// 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))
|
||||
}
|
||||
} else {
|
||||
statRepository.insertStat(Stat(today, 0, 0, 0, 0, 0))
|
||||
}
|
||||
|
||||
delay(1500)
|
||||
|
||||
_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))
|
||||
}
|
||||
} else {
|
||||
statRepository.insertStat(Stat(today, 0, 0, 0, 0, 0))
|
||||
}
|
||||
|
||||
delay(1500)
|
||||
|
||||
stateRepository.timerState.update { currentState ->
|
||||
currentState.copy(showBrandTitle = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onAction(action: TimerAction) {
|
||||
@@ -163,19 +88,15 @@ class TimerViewModel(
|
||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
val application = (this[APPLICATION_KEY] as TomatoApplication)
|
||||
val appPreferenceRepository = application.container.appPreferenceRepository
|
||||
val appStatRepository = application.container.appStatRepository
|
||||
val appTimerRepository = application.container.appTimerRepository
|
||||
val stateRepository = application.container.stateRepository
|
||||
val serviceHelper = application.container.serviceHelper
|
||||
val timerState = application.container.timerState
|
||||
val time = application.container.time
|
||||
|
||||
TimerViewModel(
|
||||
preferenceRepository = appPreferenceRepository,
|
||||
serviceHelper = serviceHelper,
|
||||
stateRepository = stateRepository,
|
||||
statRepository = appStatRepository,
|
||||
timerRepository = appTimerRepository,
|
||||
_timerState = timerState,
|
||||
_time = time
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user