diff --git a/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt b/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt
index a7a7b46..788d1af 100644
--- a/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.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.data
@@ -27,6 +37,7 @@ interface TimerRepository {
var alarmEnabled: Boolean
var vibrateEnabled: Boolean
+ var dndEnabled: Boolean
var colorScheme: ColorScheme
@@ -46,6 +57,7 @@ class AppTimerRepository : TimerRepository {
override var timerFrequency: Float = 10f
override var alarmEnabled = true
override var vibrateEnabled = true
+ override var dndEnabled: Boolean = false
override var colorScheme = lightColorScheme()
override var alarmSoundUri: Uri? =
Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI
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 0501044..8932c50 100644
--- a/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt
@@ -129,7 +129,7 @@ class TimerService : Service() {
}
}
- Actions.SKIP.toString() -> skipTimer(true)
+ Actions.SKIP.toString() -> skipScope.launch { skipTimer(true) }
Actions.STOP_ALARM.toString() -> stopAlarm()
@@ -329,50 +329,48 @@ class TimerService : Service() {
}
}
- private fun skipTimer(fromButton: Boolean = false) {
+ private suspend fun skipTimer(fromButton: Boolean = false) {
updateProgressSegments()
- skipScope.launch {
- saveTimeToDb()
- updateProgressSegments()
- showTimerNotification(0, paused = true, complete = !fromButton)
- startTime = 0L
- pauseTime = 0L
- pauseDuration = 0L
+ saveTimeToDb()
+ updateProgressSegments()
+ showTimerNotification(0, paused = true, complete = !fromButton)
+ startTime = 0L
+ pauseTime = 0L
+ pauseDuration = 0L
- cycles = (cycles + 1) % (timerRepository.sessionLength * 2)
+ cycles = (cycles + 1) % (timerRepository.sessionLength * 2)
- if (cycles % 2 == 0) {
- if (timerState.value.timerRunning) setDoNotDisturb(true)
- time = timerRepository.focusTime
- _timerState.update { currentState ->
- currentState.copy(
- timerMode = TimerMode.FOCUS,
- timeStr = millisecondsToStr(time),
- totalTime = time,
- nextTimerMode = if (cycles == (timerRepository.sessionLength - 1) * 2) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK,
- nextTimeStr = if (cycles == (timerRepository.sessionLength - 1) * 2) millisecondsToStr(
- timerRepository.longBreakTime
- ) else millisecondsToStr(
- timerRepository.shortBreakTime
- ),
- currentFocusCount = cycles / 2 + 1,
- totalFocusCount = timerRepository.sessionLength
- )
- }
- } else {
- if (timerState.value.timerRunning) setDoNotDisturb(false)
- val long = cycles == (timerRepository.sessionLength * 2) - 1
- time = if (long) timerRepository.longBreakTime else timerRepository.shortBreakTime
+ if (cycles % 2 == 0) {
+ if (timerState.value.timerRunning) setDoNotDisturb(true)
+ time = timerRepository.focusTime
+ _timerState.update { currentState ->
+ currentState.copy(
+ timerMode = TimerMode.FOCUS,
+ timeStr = millisecondsToStr(time),
+ totalTime = time,
+ nextTimerMode = if (cycles == (timerRepository.sessionLength - 1) * 2) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK,
+ nextTimeStr = if (cycles == (timerRepository.sessionLength - 1) * 2) millisecondsToStr(
+ timerRepository.longBreakTime
+ ) else millisecondsToStr(
+ timerRepository.shortBreakTime
+ ),
+ currentFocusCount = cycles / 2 + 1,
+ totalFocusCount = timerRepository.sessionLength
+ )
+ }
+ } else {
+ if (timerState.value.timerRunning) setDoNotDisturb(false)
+ val long = cycles == (timerRepository.sessionLength * 2) - 1
+ time = if (long) timerRepository.longBreakTime else timerRepository.shortBreakTime
- _timerState.update { currentState ->
- currentState.copy(
- timerMode = if (long) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK,
- timeStr = millisecondsToStr(time),
- totalTime = time,
- nextTimerMode = TimerMode.FOCUS,
- nextTimeStr = millisecondsToStr(timerRepository.focusTime)
- )
- }
+ _timerState.update { currentState ->
+ currentState.copy(
+ timerMode = if (long) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK,
+ timeStr = millisecondsToStr(time),
+ totalTime = time,
+ nextTimerMode = TimerMode.FOCUS,
+ nextTimeStr = millisecondsToStr(timerRepository.focusTime)
+ )
}
}
}
@@ -449,7 +447,7 @@ class TimerService : Service() {
}
private fun setDoNotDisturb(doNotDisturb: Boolean) {
- if (notificationManagerService.isNotificationPolicyAccessGranted()) {
+ if (timerRepository.dndEnabled && notificationManagerService.isNotificationPolicyAccessGranted()) {
if (doNotDisturb) {
notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS)
} else notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL)
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt
index 512df9a..a22b1f5 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt
@@ -104,6 +104,7 @@ fun SettingsScreenRoot(
val alarmEnabled by viewModel.alarmEnabled.collectAsStateWithLifecycle(true)
val vibrateEnabled by viewModel.vibrateEnabled.collectAsStateWithLifecycle(true)
+ val dndEnabled by viewModel.dndEnabled.collectAsStateWithLifecycle(false)
val alarmSound by viewModel.alarmSound.collectAsStateWithLifecycle(viewModel.currentAlarmSound)
val preferencesState by viewModel.preferencesState.collectAsStateWithLifecycle()
@@ -126,11 +127,13 @@ fun SettingsScreenRoot(
sessionsSliderState = sessionsSliderState,
alarmEnabled = alarmEnabled,
vibrateEnabled = vibrateEnabled,
+ dndEnabled = dndEnabled,
alarmSound = alarmSound,
onAlarmEnabledChange = viewModel::saveAlarmEnabled,
onVibrateEnabledChange = viewModel::saveVibrateEnabled,
onBlackThemeChange = viewModel::saveBlackTheme,
onAodEnabledChange = viewModel::saveAodEnabled,
+ onDndEnabledChange = viewModel::saveDndEnabled,
onAlarmSoundChanged = {
viewModel.saveAlarmSound(it)
Intent(context, TimerService::class.java).apply {
@@ -155,11 +158,13 @@ private fun SettingsScreen(
sessionsSliderState: SliderState,
alarmEnabled: Boolean,
vibrateEnabled: Boolean,
+ dndEnabled: Boolean,
alarmSound: String,
onAlarmEnabledChange: (Boolean) -> Unit,
onVibrateEnabledChange: (Boolean) -> Unit,
onBlackThemeChange: (Boolean) -> Unit,
onAodEnabledChange: (Boolean) -> Unit,
+ onDndEnabledChange: (Boolean) -> Unit,
onAlarmSoundChanged: (Uri?) -> Unit,
onThemeChange: (String) -> Unit,
onColorSchemeChange: (Color) -> Unit,
@@ -270,11 +275,13 @@ private fun SettingsScreen(
entry {
TimerSettings(
aodEnabled = preferencesState.aodEnabled,
+ dndEnabled = dndEnabled,
focusTimeInputFieldState = focusTimeInputFieldState,
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
sessionsSliderState = sessionsSliderState,
onAodEnabledChange = onAodEnabledChange,
+ onDndEnabledChange = onDndEnabledChange,
onBack = backStack::removeLastOrNull
)
}
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt
index 01dddba..421140c 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt
@@ -17,6 +17,11 @@
package org.nsh07.pomodoro.ui.settingsScreen.screens
+import android.app.NotificationManager
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
@@ -31,6 +36,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.input.TextFieldState
@@ -51,6 +57,7 @@ import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
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
@@ -59,6 +66,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
@@ -76,19 +84,58 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.cardShape
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.middleListItemShape
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
+
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun TimerSettings(
aodEnabled: Boolean,
+ dndEnabled: Boolean,
focusTimeInputFieldState: TextFieldState,
shortBreakTimeInputFieldState: TextFieldState,
longBreakTimeInputFieldState: TextFieldState,
sessionsSliderState: SliderState,
onAodEnabledChange: (Boolean) -> Unit,
+ onDndEnabledChange: (Boolean) -> Unit,
onBack: () -> Unit,
modifier: Modifier = Modifier
) {
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
+ val context = LocalContext.current
+ val appName = stringResource(R.string.app_name)
+ val notificationManagerService =
+ remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
+
+ LaunchedEffect(Unit) {
+ if (!notificationManagerService.isNotificationPolicyAccessGranted())
+ onDndEnabledChange(false)
+ }
+
+ val switchItems = listOf(
+ SettingsSwitchItem(
+ checked = dndEnabled,
+ icon = R.drawable.dnd,
+ label = R.string.dnd,
+ description = R.string.dnd_desc,
+ onClick = {
+ if (it && !notificationManagerService.isNotificationPolicyAccessGranted()) {
+ val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)
+ Toast.makeText(context, "Enable permission for \"$appName\"", Toast.LENGTH_LONG)
+ .show()
+ context.startActivity(intent)
+ } else if (!it && notificationManagerService.isNotificationPolicyAccessGranted()) {
+ notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL)
+ }
+ onDndEnabledChange(it)
+ }
+ ),
+ SettingsSwitchItem(
+ checked = aodEnabled,
+ icon = R.drawable.aod,
+ label = R.string.always_on_display,
+ description = R.string.always_on_display_desc,
+ onClick = onAodEnabledChange
+ )
+ )
Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) {
LargeFlexibleTopAppBar(
@@ -213,14 +260,7 @@ fun TimerSettings(
)
}
item { Spacer(Modifier.height(12.dp)) }
- item {
- val item = SettingsSwitchItem(
- checked = aodEnabled,
- icon = R.drawable.aod,
- label = R.string.always_on_display,
- description = R.string.always_on_display_desc,
- onClick = onAodEnabledChange
- )
+ itemsIndexed(switchItems) { index, item ->
ListItem(
leadingContent = {
Icon(
@@ -254,7 +294,13 @@ fun TimerSettings(
)
},
colors = listItemColors,
- modifier = Modifier.clip(cardShape)
+ modifier = Modifier.clip(
+ when (index) {
+ 0 -> topListItemShape
+ switchItems.size - 1 -> bottomListItemShape
+ else -> middleListItemShape
+ }
+ )
)
}
@@ -311,7 +357,9 @@ private fun TimerSettingsPreview() {
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
sessionsSliderState = sessionsSliderState,
aodEnabled = true,
+ dndEnabled = false,
onBack = {},
- onAodEnabledChange = {}
+ onAodEnabledChange = {},
+ onDndEnabledChange = {}
)
}
\ No newline at end of file
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt
index 76ebb8c..0a3262f 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt
@@ -85,6 +85,8 @@ class SettingsViewModel(
preferenceRepository.getBooleanPreferenceFlow("alarm_enabled").distinctUntilChanged()
val vibrateEnabled =
preferenceRepository.getBooleanPreferenceFlow("vibrate_enabled").distinctUntilChanged()
+ val dndEnabled =
+ preferenceRepository.getBooleanPreferenceFlow("dnd_enabled").distinctUntilChanged()
init {
viewModelScope.launch {
@@ -179,6 +181,13 @@ class SettingsViewModel(
}
}
+ fun saveDndEnabled(enabled: Boolean) {
+ viewModelScope.launch {
+ timerRepository.dndEnabled = enabled
+ preferenceRepository.saveBooleanPreference("dnd_enabled", enabled)
+ }
+ }
+
fun saveAlarmSound(uri: Uri?) {
viewModelScope.launch {
timerRepository.alarmSoundUri = uri
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt
index 53c23b5..2c4e493 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.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
@@ -85,6 +95,9 @@ class TimerViewModel(
timerRepository.vibrateEnabled =
preferenceRepository.getBooleanPreference("vibrate_enabled")
?: preferenceRepository.saveBooleanPreference("vibrate_enabled", true)
+ timerRepository.dndEnabled =
+ preferenceRepository.getBooleanPreference("dnd_enabled")
+ ?: preferenceRepository.saveBooleanPreference("dnd_enabled", false)
timerRepository.alarmSoundUri = (
preferenceRepository.getStringPreference("alarm_sound")
diff --git a/app/src/main/res/drawable/dnd.xml b/app/src/main/res/drawable/dnd.xml
new file mode 100644
index 0000000..16113b4
--- /dev/null
+++ b/app/src/main/res/drawable/dnd.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cf51e15..f4c25b8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -79,4 +79,6 @@
Appearance
Durations
Sound
+ Do Not Disturb
+ Turn on DND when running a Focus timer
\ No newline at end of file