diff --git a/app/src/main/java/org/nsh07/pomodoro/data/StateRepository.kt b/app/src/main/java/org/nsh07/pomodoro/data/StateRepository.kt
index 410e711..bcb9262 100644
--- a/app/src/main/java/org/nsh07/pomodoro/data/StateRepository.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/data/StateRepository.kt
@@ -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())
}
diff --git a/app/src/main/java/org/nsh07/pomodoro/service/ServiceHelper.kt b/app/src/main/java/org/nsh07/pomodoro/service/ServiceHelper.kt
index bb7bc89..a749631 100644
--- a/app/src/main/java/org/nsh07/pomodoro/service/ServiceHelper.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/service/ServiceHelper.kt
@@ -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()
diff --git a/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt b/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt
index 892d5ac..88aa940 100644
--- a/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt
@@ -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
}
}
diff --git a/app/src/main/java/org/nsh07/pomodoro/service/TimerStateSnapshot.kt b/app/src/main/java/org/nsh07/pomodoro/service/TimerStateSnapshot.kt
new file mode 100644
index 0000000..115b59c
--- /dev/null
+++ b/app/src/main/java/org/nsh07/pomodoro/service/TimerStateSnapshot.kt
@@ -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 .
+ */
+
+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
+ }
+}
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt
index 359bce6..b2539c6 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt
@@ -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
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerAction.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerAction.kt
index 2134c1c..1b542a4 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerAction.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerAction.kt
@@ -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 .
+ * 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 .
*/
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
}
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0c54743..e037f45 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -111,4 +111,6 @@
Auto start next timer
Secure AOD
Automatically lock your device after a timeout, while keeping the AOD visible
+ Timer reset
+ Undo
\ No newline at end of file