Make session length slider functional

This commit is contained in:
Nishant Mishra
2025-07-08 10:03:03 +05:30
parent 40c6608d79
commit e9d30bc0f6
9 changed files with 110 additions and 52 deletions

View File

@@ -9,7 +9,7 @@ import org.nsh07.pomodoro.ui.AppScreen
import org.nsh07.pomodoro.ui.NavItem import org.nsh07.pomodoro.ui.NavItem
import org.nsh07.pomodoro.ui.Screen import org.nsh07.pomodoro.ui.Screen
import org.nsh07.pomodoro.ui.theme.TomatoTheme import org.nsh07.pomodoro.ui.theme.TomatoTheme
import org.nsh07.pomodoro.ui.viewModel.TimerViewModel import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerViewModel
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {

View File

@@ -4,10 +4,12 @@ interface TimerRepository {
var focusTime: Int var focusTime: Int
var shortBreakTime: Int var shortBreakTime: Int
var longBreakTime: Int var longBreakTime: Int
var sessionLength: Int
} }
class AppTimerRepository : TimerRepository { class AppTimerRepository : TimerRepository {
override var focusTime = 25 * 60 * 1000 override var focusTime = 25 * 60 * 1000
override var shortBreakTime = 5 * 60 * 1000 override var shortBreakTime = 5 * 60 * 1000
override var longBreakTime = 15 * 60 * 1000 override var longBreakTime = 15 * 60 * 1000
override var sessionLength = 4
} }

View File

@@ -15,6 +15,7 @@ import androidx.compose.material3.MaterialTheme.motionScheme
import androidx.compose.material3.NavigationItemIconPosition import androidx.compose.material3.NavigationItemIconPosition
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.ShortNavigationBar import androidx.compose.material3.ShortNavigationBar
import androidx.compose.material3.ShortNavigationBarArrangement
import androidx.compose.material3.ShortNavigationBarItem import androidx.compose.material3.ShortNavigationBarItem
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
@@ -44,7 +45,7 @@ import org.nsh07.pomodoro.MainActivity.Companion.screens
import org.nsh07.pomodoro.ui.settingsScreen.SettingsScreenRoot import org.nsh07.pomodoro.ui.settingsScreen.SettingsScreenRoot
import org.nsh07.pomodoro.ui.statsScreen.StatsScreen import org.nsh07.pomodoro.ui.statsScreen.StatsScreen
import org.nsh07.pomodoro.ui.timerScreen.TimerScreen import org.nsh07.pomodoro.ui.timerScreen.TimerScreen
import org.nsh07.pomodoro.ui.viewModel.TimerViewModel import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerViewModel
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -52,7 +53,7 @@ fun AppScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: TimerViewModel = viewModel(factory = TimerViewModel.Factory) viewModel: TimerViewModel = viewModel(factory = TimerViewModel.Factory)
) { ) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.timerState.collectAsStateWithLifecycle()
val remainingTime by viewModel.time.collectAsStateWithLifecycle() val remainingTime by viewModel.time.collectAsStateWithLifecycle()
val progress by rememberUpdatedState((uiState.totalTime.toFloat() - remainingTime) / uiState.totalTime) val progress by rememberUpdatedState((uiState.totalTime.toFloat() - remainingTime) / uiState.totalTime)
@@ -78,7 +79,16 @@ fun AppScreen(
Scaffold( Scaffold(
bottomBar = { bottomBar = {
ShortNavigationBar { val wide = remember {
windowSizeClass.isWidthAtLeastBreakpoint(
WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND
)
}
ShortNavigationBar(
arrangement =
if (wide) ShortNavigationBarArrangement.Centered
else ShortNavigationBarArrangement.EqualWeight
) {
screens.forEach { screens.forEach {
val selected = backStack.last() == it.route val selected = backStack.last() == it.route
ShortNavigationBarItem( ShortNavigationBarItem(
@@ -98,10 +108,7 @@ fun AppScreen(
} }
}, },
iconPosition = iconPosition =
if (windowSizeClass.isWidthAtLeastBreakpoint( if (wide) NavigationItemIconPosition.Start
WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND
)
) NavigationItemIconPosition.Start
else NavigationItemIconPosition.Top, else NavigationItemIconPosition.Top,
label = { Text(it.label) } label = { Text(it.label) }
) )
@@ -134,7 +141,7 @@ fun AppScreen(
entryProvider = entryProvider { entryProvider = entryProvider {
entry<Screen.Timer> { entry<Screen.Timer> {
TimerScreen( TimerScreen(
uiState = uiState, timerState = uiState,
showBrandTitle = showBrandTitle, showBrandTitle = showBrandTitle,
progress = { progress }, progress = { progress },
resetTimer = viewModel::resetTimer, resetTimer = viewModel::resetTimer,

View File

@@ -25,6 +25,7 @@ import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.shapes import androidx.compose.material3.MaterialTheme.shapes
import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Slider import androidx.compose.material3.Slider
import androidx.compose.material3.SliderState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
@@ -43,10 +44,11 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import org.nsh07.pomodoro.R import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTitle import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTitle
import org.nsh07.pomodoro.ui.theme.TomatoTheme import org.nsh07.pomodoro.ui.theme.TomatoTheme
import org.nsh07.pomodoro.ui.viewModel.SettingsViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SettingsScreenRoot( fun SettingsScreenRoot(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@@ -62,10 +64,20 @@ fun SettingsScreenRoot(
viewModel.longBreakTimeTextFieldState viewModel.longBreakTimeTextFieldState
} }
val sessionsSliderState = rememberSaveable(
saver = SliderState.Saver(
viewModel.sessionsSliderState.onValueChangeFinished,
viewModel.sessionsSliderState.valueRange
)
) {
viewModel.sessionsSliderState
}
SettingsScreen( SettingsScreen(
focusTimeInputFieldState = focusTimeInputFieldState, focusTimeInputFieldState = focusTimeInputFieldState,
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
longBreakTimeInputFieldState = longBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState,
sessionsSliderState = sessionsSliderState,
modifier = modifier modifier = modifier
) )
} }
@@ -76,10 +88,10 @@ private fun SettingsScreen(
focusTimeInputFieldState: TextFieldState, focusTimeInputFieldState: TextFieldState,
shortBreakTimeInputFieldState: TextFieldState, shortBreakTimeInputFieldState: TextFieldState,
longBreakTimeInputFieldState: TextFieldState, longBreakTimeInputFieldState: TextFieldState,
sessionsSliderState: SliderState,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val sessionsSliderState = rememberSliderState(value = 3f, steps = 3, valueRange = 1f..5f)
Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) {
TopAppBar( TopAppBar(
@@ -184,11 +196,11 @@ private fun SettingsScreen(
) )
}, },
headlineContent = { headlineContent = {
Text("Sessions") Text("Session length")
}, },
supportingContent = { supportingContent = {
Column { Column {
Text("${sessionsSliderState.value.toInt()} sessions before a long break") Text("Focus intervals in one session: ${sessionsSliderState.value.toInt()}")
Slider( Slider(
state = sessionsSliderState, state = sessionsSliderState,
modifier = Modifier.padding(vertical = 4.dp) modifier = Modifier.padding(vertical = 4.dp)
@@ -202,6 +214,7 @@ private fun SettingsScreen(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Preview( @Preview(
showSystemUi = true, showSystemUi = true,
device = Devices.PIXEL_9_PRO device = Devices.PIXEL_9_PRO
@@ -213,6 +226,7 @@ fun SettingsScreenPreview() {
focusTimeInputFieldState = rememberTextFieldState((25 * 60 * 1000).toString()), focusTimeInputFieldState = rememberTextFieldState((25 * 60 * 1000).toString()),
shortBreakTimeInputFieldState = rememberTextFieldState((5 * 60 * 1000).toString()), shortBreakTimeInputFieldState = rememberTextFieldState((5 * 60 * 1000).toString()),
longBreakTimeInputFieldState = rememberTextFieldState((15 * 60 * 1000).toString()), longBreakTimeInputFieldState = rememberTextFieldState((15 * 60 * 1000).toString()),
sessionsSliderState = rememberSliderState(value = 3f, steps = 3, valueRange = 1f..5f),
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) )
} }

View File

@@ -1,6 +1,8 @@
package org.nsh07.pomodoro.ui.viewModel package org.nsh07.pomodoro.ui.settingsScreen.viewModel
import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SliderState
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@@ -8,6 +10,7 @@ import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.AP
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory import androidx.lifecycle.viewmodel.viewModelFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -15,7 +18,7 @@ import org.nsh07.pomodoro.TomatoApplication
import org.nsh07.pomodoro.data.AppPreferenceRepository import org.nsh07.pomodoro.data.AppPreferenceRepository
import org.nsh07.pomodoro.data.TimerRepository import org.nsh07.pomodoro.data.TimerRepository
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class, ExperimentalMaterial3Api::class)
class SettingsViewModel( class SettingsViewModel(
private val preferenceRepository: AppPreferenceRepository, private val preferenceRepository: AppPreferenceRepository,
private val timerRepository: TimerRepository private val timerRepository: TimerRepository
@@ -27,8 +30,15 @@ class SettingsViewModel(
val longBreakTimeTextFieldState = val longBreakTimeTextFieldState =
TextFieldState((timerRepository.longBreakTime / 60000).toString()) TextFieldState((timerRepository.longBreakTime / 60000).toString())
val sessionsSliderState = SliderState(
value = timerRepository.sessionLength.toFloat(),
steps = 4,
valueRange = 1f..6f,
onValueChangeFinished = ::updateSessionLength
)
init { init {
viewModelScope.launch { viewModelScope.launch(Dispatchers.IO) {
snapshotFlow { focusTimeTextFieldState.text } snapshotFlow { focusTimeTextFieldState.text }
.debounce(500) .debounce(500)
.collect { .collect {
@@ -39,6 +49,8 @@ class SettingsViewModel(
) )
} }
} }
}
viewModelScope.launch(Dispatchers.IO) {
snapshotFlow { shortBreakTimeTextFieldState.text } snapshotFlow { shortBreakTimeTextFieldState.text }
.debounce(500) .debounce(500)
.collect { .collect {
@@ -49,6 +61,8 @@ class SettingsViewModel(
) )
} }
} }
}
viewModelScope.launch(Dispatchers.IO) {
snapshotFlow { longBreakTimeTextFieldState.text } snapshotFlow { longBreakTimeTextFieldState.text }
.debounce(500) .debounce(500)
.collect { .collect {
@@ -62,6 +76,15 @@ class SettingsViewModel(
} }
} }
private fun updateSessionLength() {
viewModelScope.launch {
timerRepository.sessionLength = preferenceRepository.saveIntPreference(
"session_length",
sessionsSliderState.value.toInt()
)
}
}
companion object { companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory { val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer { initializer {

View File

@@ -53,13 +53,13 @@ import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.ui.theme.AppFonts.openRundeClock import org.nsh07.pomodoro.ui.theme.AppFonts.openRundeClock
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTitle import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTitle
import org.nsh07.pomodoro.ui.theme.TomatoTheme import org.nsh07.pomodoro.ui.theme.TomatoTheme
import org.nsh07.pomodoro.ui.viewModel.TimerMode import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerMode
import org.nsh07.pomodoro.ui.viewModel.UiState import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun TimerScreen( fun TimerScreen(
uiState: UiState, timerState: TimerState,
showBrandTitle: Boolean, showBrandTitle: Boolean,
progress: () -> Float, progress: () -> Float,
resetTimer: () -> Unit, resetTimer: () -> Unit,
@@ -70,17 +70,17 @@ fun TimerScreen(
val motionScheme = motionScheme val motionScheme = motionScheme
val color by animateColorAsState( val color by animateColorAsState(
if (uiState.timerMode == TimerMode.FOCUS) colorScheme.primary if (timerState.timerMode == TimerMode.FOCUS) colorScheme.primary
else colorScheme.tertiary, else colorScheme.tertiary,
animationSpec = motionScheme.slowEffectsSpec() animationSpec = motionScheme.slowEffectsSpec()
) )
val onColor by animateColorAsState( val onColor by animateColorAsState(
if (uiState.timerMode == TimerMode.FOCUS) colorScheme.onPrimary if (timerState.timerMode == TimerMode.FOCUS) colorScheme.onPrimary
else colorScheme.onTertiary, else colorScheme.onTertiary,
animationSpec = motionScheme.slowEffectsSpec() animationSpec = motionScheme.slowEffectsSpec()
) )
val colorContainer by animateColorAsState( val colorContainer by animateColorAsState(
if (uiState.timerMode == TimerMode.FOCUS) colorScheme.secondaryContainer if (timerState.timerMode == TimerMode.FOCUS) colorScheme.secondaryContainer
else colorScheme.tertiaryContainer, else colorScheme.tertiaryContainer,
animationSpec = motionScheme.slowEffectsSpec() animationSpec = motionScheme.slowEffectsSpec()
) )
@@ -89,7 +89,7 @@ fun TimerScreen(
TopAppBar( TopAppBar(
title = { title = {
AnimatedContent( AnimatedContent(
if (!showBrandTitle) uiState.timerMode else TimerMode.BRAND, if (!showBrandTitle) timerState.timerMode else TimerMode.BRAND,
transitionSpec = { transitionSpec = {
slideInVertically( slideInVertically(
animationSpec = motionScheme.slowSpatialSpec(), animationSpec = motionScheme.slowSpatialSpec(),
@@ -166,7 +166,7 @@ fun TimerScreen(
) { ) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box(contentAlignment = Alignment.Center) { Box(contentAlignment = Alignment.Center) {
if (uiState.timerMode == TimerMode.FOCUS) { if (timerState.timerMode == TimerMode.FOCUS) {
CircularProgressIndicator( CircularProgressIndicator(
progress = progress, progress = progress,
modifier = Modifier modifier = Modifier
@@ -204,7 +204,7 @@ fun TimerScreen(
) )
} }
Text( Text(
text = uiState.timeStr, text = timerState.timeStr,
style = TextStyle( style = TextStyle(
fontFamily = openRundeClock, fontFamily = openRundeClock,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
@@ -246,7 +246,7 @@ fun TimerScreen(
{ {
FilledIconToggleButton( FilledIconToggleButton(
onCheckedChange = { toggleTimer() }, onCheckedChange = { toggleTimer() },
checked = uiState.timerRunning, checked = timerState.timerRunning,
colors = IconButtonDefaults.filledIconToggleButtonColors( colors = IconButtonDefaults.filledIconToggleButtonColors(
checkedContainerColor = color, checkedContainerColor = color,
checkedContentColor = onColor checkedContentColor = onColor
@@ -257,7 +257,7 @@ fun TimerScreen(
.size(width = 128.dp, height = 96.dp) .size(width = 128.dp, height = 96.dp)
.animateWidth(interactionSources[0]) .animateWidth(interactionSources[0])
) { ) {
if (uiState.timerRunning) { if (timerState.timerRunning) {
Icon( Icon(
painterResource(R.drawable.pause_large), painterResource(R.drawable.pause_large),
contentDescription = "Pause", contentDescription = "Pause",
@@ -275,7 +275,7 @@ fun TimerScreen(
{ state -> { state ->
DropdownMenuItem( DropdownMenuItem(
leadingIcon = { leadingIcon = {
if (uiState.timerRunning) { if (timerState.timerRunning) {
Icon( Icon(
painterResource(R.drawable.pause), painterResource(R.drawable.pause),
contentDescription = "Pause" contentDescription = "Pause"
@@ -287,7 +287,7 @@ fun TimerScreen(
) )
} }
}, },
text = { Text(if (uiState.timerRunning) "Pause" else "Play") }, text = { Text(if (timerState.timerRunning) "Pause" else "Play") },
onClick = { onClick = {
toggleTimer() toggleTimer()
state.dismiss() state.dismiss()
@@ -377,17 +377,17 @@ fun TimerScreen(
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Up next", style = typography.titleSmall) Text("Up next", style = typography.titleSmall)
Text( Text(
uiState.nextTimeStr, timerState.nextTimeStr,
style = TextStyle( style = TextStyle(
fontFamily = openRundeClock, fontFamily = openRundeClock,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 22.sp, fontSize = 22.sp,
lineHeight = 28.sp, lineHeight = 28.sp,
color = if (uiState.nextTimerMode == TimerMode.FOCUS) colorScheme.primary else colorScheme.tertiary color = if (timerState.nextTimerMode == TimerMode.FOCUS) colorScheme.primary else colorScheme.tertiary
) )
) )
Text( Text(
when (uiState.nextTimerMode) { when (timerState.nextTimerMode) {
TimerMode.FOCUS -> "Focus" TimerMode.FOCUS -> "Focus"
TimerMode.SHORT_BREAK -> "Short Break" TimerMode.SHORT_BREAK -> "Short Break"
else -> "Long Break" else -> "Long Break"
@@ -405,12 +405,12 @@ fun TimerScreen(
) )
@Composable @Composable
fun TimerScreenPreview() { fun TimerScreenPreview() {
val uiState = UiState( val timerState = TimerState(
timeStr = "03:34", nextTimeStr = "5:00", timerMode = TimerMode.FOCUS, timerRunning = true timeStr = "03:34", nextTimeStr = "5:00", timerMode = TimerMode.FOCUS, timerRunning = true
) )
TomatoTheme { TomatoTheme {
TimerScreen( TimerScreen(
uiState, timerState,
false, false,
{ 0.3f }, { 0.3f },
{}, {},

View File

@@ -1,6 +1,6 @@
package org.nsh07.pomodoro.ui.viewModel package org.nsh07.pomodoro.ui.timerScreen.viewModel
data class UiState( data class TimerState(
val timerMode: TimerMode = TimerMode.FOCUS, val timerMode: TimerMode = TimerMode.FOCUS,
val timeStr: String = "25:00", val timeStr: String = "25:00",
val totalTime: Int = 25 * 60, val totalTime: Int = 25 * 60,

View File

@@ -1,4 +1,4 @@
package org.nsh07.pomodoro.ui.viewModel package org.nsh07.pomodoro.ui.timerScreen.viewModel
import android.os.SystemClock import android.os.SystemClock
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@@ -34,19 +34,21 @@ class TimerViewModel(
?: preferenceRepository.saveIntPreference("short_break_time", timerRepository.shortBreakTime) ?: preferenceRepository.saveIntPreference("short_break_time", timerRepository.shortBreakTime)
timerRepository.longBreakTime = preferenceRepository.getIntPreference("long_break_time") timerRepository.longBreakTime = preferenceRepository.getIntPreference("long_break_time")
?: preferenceRepository.saveIntPreference("long_break_time", timerRepository.longBreakTime) ?: preferenceRepository.saveIntPreference("long_break_time", timerRepository.longBreakTime)
timerRepository.sessionLength = preferenceRepository.getIntPreference("session_length")
?: preferenceRepository.saveIntPreference("session_length", timerRepository.sessionLength)
resetTimer() resetTimer()
} }
} }
private val _uiState = MutableStateFlow( private val _timerState = MutableStateFlow(
UiState( TimerState(
totalTime = timerRepository.focusTime, totalTime = timerRepository.focusTime,
timeStr = millisecondsToStr(timerRepository.focusTime), timeStr = millisecondsToStr(timerRepository.focusTime),
nextTimeStr = millisecondsToStr(timerRepository.shortBreakTime) nextTimeStr = millisecondsToStr(timerRepository.shortBreakTime)
) )
) )
val uiState: StateFlow<UiState> = _uiState.asStateFlow() val timerState: StateFlow<TimerState> = _timerState.asStateFlow()
var timerJob: Job? = null var timerJob: Job? = null
private val _time = MutableStateFlow(timerRepository.focusTime) private val _time = MutableStateFlow(timerRepository.focusTime)
@@ -64,7 +66,7 @@ class TimerViewModel(
pauseTime = 0L pauseTime = 0L
pauseDuration = 0L pauseDuration = 0L
_uiState.update { currentState -> _timerState.update { currentState ->
currentState.copy( currentState.copy(
timerMode = TimerMode.FOCUS, timerMode = TimerMode.FOCUS,
timeStr = millisecondsToStr(time.value), timeStr = millisecondsToStr(time.value),
@@ -79,11 +81,11 @@ class TimerViewModel(
startTime = 0L startTime = 0L
pauseTime = 0L pauseTime = 0L
pauseDuration = 0L pauseDuration = 0L
cycles = (cycles + 1) % 8 cycles = (cycles + 1) % (timerRepository.sessionLength * 2)
if (cycles % 2 == 0) { if (cycles % 2 == 0) {
_time.update { timerRepository.focusTime } _time.update { timerRepository.focusTime }
_uiState.update { currentState -> _timerState.update { currentState ->
currentState.copy( currentState.copy(
timerMode = TimerMode.FOCUS, timerMode = TimerMode.FOCUS,
timeStr = millisecondsToStr(time.value), timeStr = millisecondsToStr(time.value),
@@ -97,10 +99,10 @@ class TimerViewModel(
) )
} }
} else { } else {
val long = cycles == 7 val long = cycles == (timerRepository.sessionLength * 2) - 1
_time.update { if (long) timerRepository.longBreakTime else timerRepository.shortBreakTime } _time.update { if (long) timerRepository.longBreakTime else timerRepository.shortBreakTime }
_uiState.update { currentState -> _timerState.update { currentState ->
currentState.copy( currentState.copy(
timerMode = if (long) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK, timerMode = if (long) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK,
timeStr = millisecondsToStr(time.value), timeStr = millisecondsToStr(time.value),
@@ -113,23 +115,23 @@ class TimerViewModel(
} }
fun toggleTimer() { fun toggleTimer() {
if (uiState.value.timerRunning) { if (timerState.value.timerRunning) {
_uiState.update { currentState -> _timerState.update { currentState ->
currentState.copy(timerRunning = false) currentState.copy(timerRunning = false)
} }
timerJob?.cancel() timerJob?.cancel()
pauseTime = SystemClock.elapsedRealtime() pauseTime = SystemClock.elapsedRealtime()
} else { } else {
_uiState.update { it.copy(timerRunning = true) } _timerState.update { it.copy(timerRunning = true) }
if (pauseTime != 0L) pauseDuration += SystemClock.elapsedRealtime() - pauseTime if (pauseTime != 0L) pauseDuration += SystemClock.elapsedRealtime() - pauseTime
timerJob = viewModelScope.launch { timerJob = viewModelScope.launch {
while (true) { while (true) {
if (!uiState.value.timerRunning) break if (!timerState.value.timerRunning) break
if (startTime == 0L) startTime = SystemClock.elapsedRealtime() if (startTime == 0L) startTime = SystemClock.elapsedRealtime()
_time.update { _time.update {
when (uiState.value.timerMode) { when (timerState.value.timerMode) {
TimerMode.FOCUS -> TimerMode.FOCUS ->
timerRepository.focusTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration).toInt() timerRepository.focusTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration).toInt()
@@ -144,12 +146,12 @@ class TimerViewModel(
if (time.value < 0) { if (time.value < 0) {
skipTimer() skipTimer()
_uiState.update { currentState -> _timerState.update { currentState ->
currentState.copy(timerRunning = false) currentState.copy(timerRunning = false)
} }
timerJob?.cancel() timerJob?.cancel()
} else { } else {
_uiState.update { currentState -> _timerState.update { currentState ->
currentState.copy( currentState.copy(
timeStr = millisecondsToStr(time.value) timeStr = millisecondsToStr(time.value)
) )

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#000000"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M480,680q17,0 28.5,-11.5T520,640v-160q0,-17 -11.5,-28.5T480,440q-17,0 -28.5,11.5T440,480v160q0,17 11.5,28.5T480,680ZM480,360q17,0 28.5,-11.5T520,320q0,-17 -11.5,-28.5T480,280q-17,0 -28.5,11.5T440,320q0,17 11.5,28.5T480,360ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880Z" />
</vector>