refactor: Migrate TimerViewModel from MVVM to MVI
Also fix a bug in the StatDao that prevented app from compiling
This commit is contained in:
@@ -39,7 +39,7 @@ interface StatDao {
|
||||
@Query("SELECT date, focusTimeQ1 + focusTimeQ2 + focusTimeQ3 + focusTimeQ4 as focusTime, breakTime FROM stat")
|
||||
fun getAllStatsSummary(): Flow<List<StatSummary>>
|
||||
|
||||
@Query("SELECT AVG(focusTimeQ1), AVG(focusTimeQ2), AVG(focusTimeQ3), AVG(focusTimeQ4) FROM stat")
|
||||
@Query("SELECT AVG(focusTimeQ1) AS focusTimeQ1, AVG(focusTimeQ2) AS focusTimeQ2, AVG(focusTimeQ3) AS focusTimeQ3, AVG(focusTimeQ4) AS focusTimeQ4 FROM stat")
|
||||
fun getAvgFocusTimes(): Flow<StatFocusTime?>
|
||||
|
||||
@Query("SELECT EXISTS (SELECT * FROM stat WHERE date = :date)")
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.ui
|
||||
|
||||
import androidx.compose.animation.ContentTransform
|
||||
@@ -22,10 +29,8 @@ import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
@@ -38,9 +43,6 @@ import androidx.navigation3.runtime.entryProvider
|
||||
import androidx.navigation3.runtime.rememberNavBackStack
|
||||
import androidx.navigation3.ui.NavDisplay
|
||||
import androidx.window.core.layout.WindowSizeClass
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.nsh07.pomodoro.MainActivity.Companion.screens
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.SettingsScreenRoot
|
||||
import org.nsh07.pomodoro.ui.statsScreen.StatsScreen
|
||||
@@ -57,20 +59,12 @@ fun AppScreen(
|
||||
val remainingTime by viewModel.time.collectAsStateWithLifecycle()
|
||||
|
||||
val progress by rememberUpdatedState((uiState.totalTime.toFloat() - remainingTime) / uiState.totalTime)
|
||||
var showBrandTitle by remember { mutableStateOf(true) }
|
||||
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val motionScheme = motionScheme
|
||||
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
withContext(Dispatchers.IO) {
|
||||
delay(1500)
|
||||
showBrandTitle = false
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(uiState.timerMode) {
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
@@ -142,11 +136,8 @@ fun AppScreen(
|
||||
entry<Screen.Timer> {
|
||||
TimerScreen(
|
||||
timerState = uiState,
|
||||
showBrandTitle = showBrandTitle,
|
||||
progress = { progress },
|
||||
resetTimer = viewModel::resetTimer,
|
||||
skipTimer = viewModel::skipTimer,
|
||||
toggleTimer = viewModel::toggleTimer,
|
||||
onAction = viewModel::onAction,
|
||||
modifier = modifier.padding(
|
||||
start = contentPadding.calculateStartPadding(layoutDirection),
|
||||
end = contentPadding.calculateEndPadding(layoutDirection),
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.ui.timerScreen
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
@@ -53,6 +60,7 @@ import org.nsh07.pomodoro.R
|
||||
import org.nsh07.pomodoro.ui.theme.AppFonts.openRundeClock
|
||||
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTitle
|
||||
import org.nsh07.pomodoro.ui.theme.TomatoTheme
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerMode
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
|
||||
|
||||
@@ -60,11 +68,8 @@ import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
|
||||
@Composable
|
||||
fun TimerScreen(
|
||||
timerState: TimerState,
|
||||
showBrandTitle: Boolean,
|
||||
progress: () -> Float,
|
||||
resetTimer: () -> Unit,
|
||||
skipTimer: () -> Unit,
|
||||
toggleTimer: () -> Unit,
|
||||
onAction: (TimerAction) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val motionScheme = motionScheme
|
||||
@@ -89,7 +94,7 @@ fun TimerScreen(
|
||||
TopAppBar(
|
||||
title = {
|
||||
AnimatedContent(
|
||||
if (!showBrandTitle) timerState.timerMode else TimerMode.BRAND,
|
||||
if (!timerState.showBrandTitle) timerState.timerMode else TimerMode.BRAND,
|
||||
transitionSpec = {
|
||||
slideInVertically(
|
||||
animationSpec = motionScheme.slowSpatialSpec(),
|
||||
@@ -245,7 +250,7 @@ fun TimerScreen(
|
||||
customItem(
|
||||
{
|
||||
FilledIconToggleButton(
|
||||
onCheckedChange = { toggleTimer() },
|
||||
onCheckedChange = { onAction(TimerAction.ToggleTimer) },
|
||||
checked = timerState.timerRunning,
|
||||
colors = IconButtonDefaults.filledIconToggleButtonColors(
|
||||
checkedContainerColor = color,
|
||||
@@ -289,7 +294,7 @@ fun TimerScreen(
|
||||
},
|
||||
text = { Text(if (timerState.timerRunning) "Pause" else "Play") },
|
||||
onClick = {
|
||||
toggleTimer()
|
||||
onAction(TimerAction.ToggleTimer)
|
||||
state.dismiss()
|
||||
}
|
||||
)
|
||||
@@ -299,7 +304,7 @@ fun TimerScreen(
|
||||
customItem(
|
||||
{
|
||||
FilledTonalIconButton(
|
||||
onClick = resetTimer,
|
||||
onClick = { onAction(TimerAction.ResetTimer) },
|
||||
colors = IconButtonDefaults.filledTonalIconButtonColors(
|
||||
containerColor = colorContainer
|
||||
),
|
||||
@@ -326,7 +331,7 @@ fun TimerScreen(
|
||||
},
|
||||
text = { Text("Restart") },
|
||||
onClick = {
|
||||
resetTimer()
|
||||
onAction(TimerAction.ResetTimer)
|
||||
state.dismiss()
|
||||
}
|
||||
)
|
||||
@@ -336,7 +341,7 @@ fun TimerScreen(
|
||||
customItem(
|
||||
{
|
||||
FilledTonalIconButton(
|
||||
onClick = skipTimer,
|
||||
onClick = { onAction(TimerAction.SkipTimer) },
|
||||
colors = IconButtonDefaults.filledTonalIconButtonColors(
|
||||
containerColor = colorContainer
|
||||
),
|
||||
@@ -363,7 +368,7 @@ fun TimerScreen(
|
||||
},
|
||||
text = { Text("Skip to next") },
|
||||
onClick = {
|
||||
skipTimer()
|
||||
onAction(TimerAction.SkipTimer)
|
||||
state.dismiss()
|
||||
}
|
||||
)
|
||||
@@ -411,10 +416,7 @@ fun TimerScreenPreview() {
|
||||
TomatoTheme {
|
||||
TimerScreen(
|
||||
timerState,
|
||||
false,
|
||||
{ 0.3f },
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.ui.timerScreen.viewModel
|
||||
|
||||
sealed interface TimerAction {
|
||||
data object ResetTimer : TimerAction
|
||||
data object SkipTimer : TimerAction
|
||||
data object ToggleTimer : TimerAction
|
||||
}
|
||||
@@ -13,7 +13,8 @@ data class TimerState(
|
||||
val totalTime: Long = 25 * 60,
|
||||
val timerRunning: Boolean = false,
|
||||
val nextTimerMode: TimerMode = TimerMode.SHORT_BREAK,
|
||||
val nextTimeStr: String = "5:00"
|
||||
val nextTimeStr: String = "5:00",
|
||||
val showBrandTitle: Boolean = true
|
||||
)
|
||||
|
||||
enum class TimerMode {
|
||||
|
||||
@@ -82,10 +82,24 @@ class TimerViewModel(
|
||||
)
|
||||
|
||||
resetTimer()
|
||||
|
||||
delay(1500)
|
||||
|
||||
_timerState.update { currentState ->
|
||||
currentState.copy(showBrandTitle = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resetTimer() {
|
||||
fun onAction(action: TimerAction) {
|
||||
when (action) {
|
||||
TimerAction.ResetTimer -> resetTimer()
|
||||
TimerAction.SkipTimer -> skipTimer()
|
||||
TimerAction.ToggleTimer -> toggleTimer()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetTimer() {
|
||||
viewModelScope.launch {
|
||||
saveTimeToDb()
|
||||
_time.update { timerRepository.focusTime }
|
||||
@@ -106,7 +120,7 @@ class TimerViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun skipTimer() {
|
||||
private fun skipTimer() {
|
||||
viewModelScope.launch {
|
||||
saveTimeToDb()
|
||||
startTime = 0L
|
||||
@@ -147,7 +161,7 @@ class TimerViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleTimer() {
|
||||
private fun toggleTimer() {
|
||||
if (timerState.value.timerRunning) {
|
||||
_timerState.update { currentState ->
|
||||
currentState.copy(timerRunning = false)
|
||||
|
||||
Reference in New Issue
Block a user