Make session length slider functional
This commit is contained in:
@@ -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() {
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -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 },
|
||||||
{},
|
{},
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -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)
|
||||||
)
|
)
|
||||||
10
app/src/main/res/drawable/info.xml
Normal file
10
app/src/main/res/drawable/info.xml
Normal 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>
|
||||||
Reference in New Issue
Block a user