@@ -20,6 +20,7 @@ package org.nsh07.pomodoro.data
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.nsh07.pomodoro.service.TimerStateSnapshot
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsState
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
|
||||
|
||||
@@ -28,4 +29,6 @@ class StateRepository {
|
||||
val settingsState = MutableStateFlow(SettingsState())
|
||||
var timerFrequency: Float = 60f
|
||||
var colorScheme: ColorScheme = lightColorScheme()
|
||||
var timerStateSnapshot: TimerStateSnapshot =
|
||||
TimerStateSnapshot(time = 0, timerState = TimerState())
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ class ServiceHelper(private val context: Context) {
|
||||
context.startService(it)
|
||||
}
|
||||
|
||||
TimerAction.UndoReset ->
|
||||
Intent(context, TimerService::class.java).also {
|
||||
it.action = TimerService.Actions.UNDO_RESET.toString()
|
||||
context.startService(it)
|
||||
}
|
||||
|
||||
is TimerAction.SkipTimer ->
|
||||
Intent(context, TimerService::class.java).also {
|
||||
it.action = TimerService.Actions.SKIP.toString()
|
||||
|
||||
@@ -75,6 +75,8 @@ class TimerService : Service() {
|
||||
|
||||
private var lastSavedDuration = 0L
|
||||
|
||||
private val timerStateSnapshot by lazy { stateRepository.timerStateSnapshot }
|
||||
|
||||
private val saveLock = Mutex()
|
||||
private var job = SupervisorJob()
|
||||
private val timerScope = CoroutineScope(Dispatchers.IO + job)
|
||||
@@ -134,6 +136,8 @@ class TimerService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
Actions.UNDO_RESET.toString() -> undoReset()
|
||||
|
||||
Actions.SKIP.toString() -> skipScope.launch { skipTimer(true) }
|
||||
|
||||
Actions.STOP_ALARM.toString() -> stopAlarm()
|
||||
@@ -326,6 +330,16 @@ class TimerService : Service() {
|
||||
private suspend fun resetTimer() {
|
||||
val settingsState = _settingsState.value
|
||||
|
||||
timerStateSnapshot.save(
|
||||
lastSavedDuration,
|
||||
time,
|
||||
cycles,
|
||||
startTime,
|
||||
pauseTime,
|
||||
pauseDuration,
|
||||
_timerState.value
|
||||
)
|
||||
|
||||
saveTimeToDb()
|
||||
lastSavedDuration = 0
|
||||
time = settingsState.focusTime
|
||||
@@ -349,6 +363,16 @@ class TimerService : Service() {
|
||||
updateProgressSegments()
|
||||
}
|
||||
|
||||
private fun undoReset() {
|
||||
lastSavedDuration = timerStateSnapshot.lastSavedDuration
|
||||
time = timerStateSnapshot.time
|
||||
cycles = timerStateSnapshot.cycles
|
||||
startTime = timerStateSnapshot.startTime
|
||||
pauseTime = timerStateSnapshot.pauseTime
|
||||
pauseDuration = timerStateSnapshot.pauseDuration
|
||||
_timerState.update { timerStateSnapshot.timerState }
|
||||
}
|
||||
|
||||
private suspend fun skipTimer(fromButton: Boolean = false) {
|
||||
val settingsState = _settingsState.value
|
||||
saveTimeToDb()
|
||||
@@ -517,6 +541,6 @@ class TimerService : Service() {
|
||||
}
|
||||
|
||||
enum class Actions {
|
||||
TOGGLE, SKIP, RESET, STOP_ALARM, UPDATE_ALARM_TONE
|
||||
TOGGLE, SKIP, RESET, UNDO_RESET, STOP_ALARM, UPDATE_ALARM_TONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.service
|
||||
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
|
||||
|
||||
data class TimerStateSnapshot(
|
||||
var lastSavedDuration: Long = 0L,
|
||||
var time: Long,
|
||||
var cycles: Int = 0,
|
||||
var startTime: Long = 0L,
|
||||
var pauseTime: Long = 0L,
|
||||
var pauseDuration: Long = 0L,
|
||||
var timerState: TimerState
|
||||
) {
|
||||
fun save(
|
||||
lastSavedDuration: Long,
|
||||
time: Long,
|
||||
cycles: Int,
|
||||
startTime: Long,
|
||||
pauseTime: Long,
|
||||
pauseDuration: Long,
|
||||
timerState: TimerState
|
||||
) {
|
||||
this.lastSavedDuration = lastSavedDuration
|
||||
this.time = time
|
||||
this.cycles = cycles
|
||||
this.startTime = startTime
|
||||
this.pauseTime = pauseTime
|
||||
this.pauseDuration = pauseDuration
|
||||
this.timerState = timerState
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.nsh07.pomodoro.ui.timerScreen
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -65,6 +66,10 @@ import androidx.compose.material3.MaterialTheme.motionScheme
|
||||
import androidx.compose.material3.MaterialTheme.shapes
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
@@ -74,6 +79,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
||||
@@ -83,6 +89,7 @@ import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
@@ -94,8 +101,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation3.ui.LocalNavAnimatedContentScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.nsh07.pomodoro.R
|
||||
import org.nsh07.pomodoro.ui.mergePaddingValues
|
||||
import org.nsh07.pomodoro.ui.theme.AppFonts.googleFlex600
|
||||
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
||||
import org.nsh07.pomodoro.ui.theme.TomatoTheme
|
||||
@@ -103,7 +110,6 @@ import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerMode
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun SharedTransitionScope.TimerScreen(
|
||||
@@ -115,7 +121,9 @@ fun SharedTransitionScope.TimerScreen(
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val motionScheme = motionScheme
|
||||
val scope = rememberCoroutineScope()
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val context = LocalContext.current
|
||||
|
||||
val color by animateColorAsState(
|
||||
if (timerState.timerMode == TimerMode.FOCUS) colorScheme.primary
|
||||
@@ -139,6 +147,7 @@ fun SharedTransitionScope.TimerScreen(
|
||||
)
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -215,16 +224,16 @@ fun SharedTransitionScope.TimerScreen(
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
},
|
||||
bottomBar = { Spacer(Modifier.height(contentPadding.calculateBottomPadding())) },
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
modifier = modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
) { innerPadding ->
|
||||
val insets = mergePaddingValues(innerPadding, contentPadding)
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = CenterHorizontally,
|
||||
contentPadding = insets,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
contentPadding = innerPadding,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
item {
|
||||
Column(horizontalAlignment = CenterHorizontally) {
|
||||
@@ -411,6 +420,19 @@ fun SharedTransitionScope.TimerScreen(
|
||||
onClick = {
|
||||
onAction(TimerAction.ResetTimer)
|
||||
haptic.performHapticFeedback(HapticFeedbackType.VirtualKey)
|
||||
|
||||
@SuppressLint("LocalContextGetResourceValueCall")
|
||||
scope.launch {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
context.getString(R.string.timer_reset_message),
|
||||
actionLabel = context.getString(R.string.undo),
|
||||
withDismissAction = true,
|
||||
duration = SnackbarDuration.Long
|
||||
)
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
onAction(TimerAction.UndoReset)
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = IconButtonDefaults.filledTonalIconButtonColors(
|
||||
containerColor = colorContainer
|
||||
|
||||
@@ -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
|
||||
@@ -11,6 +21,7 @@ sealed interface TimerAction {
|
||||
data class SkipTimer(val fromButton: Boolean) : TimerAction
|
||||
|
||||
data object ResetTimer : TimerAction
|
||||
data object UndoReset : TimerAction
|
||||
data object StopAlarm : TimerAction
|
||||
data object ToggleTimer : TimerAction
|
||||
}
|
||||
@@ -111,4 +111,6 @@
|
||||
<string name="auto_start_next_timer">Auto start next timer</string>
|
||||
<string name="secure_aod">Secure AOD</string>
|
||||
<string name="secure_aod_desc">Automatically lock your device after a timeout, while keeping the AOD visible</string>
|
||||
<string name="timer_reset_message">Timer reset</string>
|
||||
<string name="undo">Undo</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user