Merge pull request #94 from nsh07/auto-dnd
feat(system): automatically turn on DND in focus mode
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.nsh07.pomodoro.data
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -34,6 +35,7 @@ interface AppContainer {
|
||||
val appStatRepository: AppStatRepository
|
||||
val appTimerRepository: AppTimerRepository
|
||||
val notificationManager: NotificationManagerCompat
|
||||
val notificationManagerService: NotificationManager
|
||||
val notificationBuilder: NotificationCompat.Builder
|
||||
val timerState: MutableStateFlow<TimerState>
|
||||
val time: MutableStateFlow<Long>
|
||||
@@ -56,6 +58,9 @@ class DefaultAppContainer(context: Context) : AppContainer {
|
||||
NotificationManagerCompat.from(context)
|
||||
}
|
||||
|
||||
override val notificationManagerService: NotificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
override val notificationBuilder: NotificationCompat.Builder by lazy {
|
||||
NotificationCompat.Builder(context, "timer")
|
||||
.setSmallIcon(R.drawable.tomato_logo_notification)
|
||||
|
||||
@@ -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.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
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.nsh07.pomodoro.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.media.AudioAttributes
|
||||
@@ -30,7 +31,6 @@ import android.os.Vibrator
|
||||
import android.os.VibratorManager
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -53,7 +53,8 @@ class TimerService : Service() {
|
||||
|
||||
private val timerRepository by lazy { appContainer.appTimerRepository }
|
||||
private val statRepository by lazy { appContainer.appStatRepository }
|
||||
private val notificationManager by lazy { NotificationManagerCompat.from(this) }
|
||||
private val notificationManager by lazy { appContainer.notificationManager }
|
||||
private val notificationManagerService by lazy { appContainer.notificationManagerService }
|
||||
private val notificationBuilder by lazy { appContainer.notificationBuilder }
|
||||
private val _timerState by lazy { appContainer.timerState }
|
||||
private val _time by lazy { appContainer.time }
|
||||
@@ -106,6 +107,7 @@ class TimerService : Service() {
|
||||
runBlocking {
|
||||
job.cancel()
|
||||
saveTimeToDb()
|
||||
setDoNotDisturb(false)
|
||||
notificationManager.cancel(1)
|
||||
alarm?.release()
|
||||
}
|
||||
@@ -127,7 +129,7 @@ class TimerService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
Actions.SKIP.toString() -> skipTimer(true)
|
||||
Actions.SKIP.toString() -> skipScope.launch { skipTimer(true) }
|
||||
|
||||
Actions.STOP_ALARM.toString() -> stopAlarm()
|
||||
|
||||
@@ -140,6 +142,7 @@ class TimerService : Service() {
|
||||
updateProgressSegments()
|
||||
|
||||
if (timerState.value.timerRunning) {
|
||||
setDoNotDisturb(false)
|
||||
notificationBuilder.clearActions().addTimerActions(
|
||||
this, R.drawable.play, getString(R.string.start)
|
||||
)
|
||||
@@ -149,6 +152,8 @@ class TimerService : Service() {
|
||||
}
|
||||
pauseTime = SystemClock.elapsedRealtime()
|
||||
} else {
|
||||
if (timerState.value.timerMode == TimerMode.FOCUS) setDoNotDisturb(true)
|
||||
else setDoNotDisturb(false)
|
||||
notificationBuilder.clearActions().addTimerActions(
|
||||
this, R.drawable.pause, getString(R.string.stop)
|
||||
)
|
||||
@@ -324,48 +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) {
|
||||
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 {
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -441,7 +446,15 @@ class TimerService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAlarmTone() {
|
||||
private fun setDoNotDisturb(doNotDisturb: Boolean) {
|
||||
if (timerRepository.dndEnabled && notificationManagerService.isNotificationPolicyAccessGranted()) {
|
||||
if (doNotDisturb) {
|
||||
notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS)
|
||||
} else notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAlarmTone() {
|
||||
alarm?.release()
|
||||
alarm = initializeMediaPlayer()
|
||||
}
|
||||
|
||||
@@ -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<Screen.Settings.Timer> {
|
||||
TimerSettings(
|
||||
aodEnabled = preferencesState.aodEnabled,
|
||||
dndEnabled = dndEnabled,
|
||||
focusTimeInputFieldState = focusTimeInputFieldState,
|
||||
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
|
||||
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
|
||||
sessionsSliderState = sessionsSliderState,
|
||||
onAodEnabledChange = onAodEnabledChange,
|
||||
onDndEnabledChange = onDndEnabledChange,
|
||||
onBack = backStack::removeLastOrNull
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Button
|
||||
@@ -123,8 +124,9 @@ fun AboutCard(modifier: Modifier = Modifier) {
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painterResource(R.drawable.coffee),
|
||||
painterResource(R.drawable.bmc),
|
||||
contentDescription = "Buy me a coffee",
|
||||
modifier = Modifier.height(24.dp)
|
||||
)
|
||||
|
||||
Text(text = "Buy me a coffee")
|
||||
|
||||
@@ -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 = {}
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -92,6 +102,7 @@ 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(
|
||||
|
||||
@@ -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
|
||||
@@ -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")
|
||||
|
||||
30
app/src/main/res/drawable/bmc.xml
Normal file
30
app/src/main/res/drawable/bmc.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1279"
|
||||
android:viewportHeight="1279">
|
||||
<path
|
||||
android:fillAlpha="0.7"
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m670.4,590.8c-45.9,19.7 -98.1,42 -165.6,42 -28.3,-0.1 -56.4,-3.9 -83.6,-11.5l46.7,479.8c1.7,20 10.8,38.8 25.6,52.4 14.8,13.6 34.2,21.2 54.3,21.2 0,0 66.3,3.4 88.4,3.4 23.8,0 95.2,-3.4 95.2,-3.4 20.1,0 39.5,-7.6 54.3,-21.2 14.8,-13.6 23.9,-32.3 25.6,-52.4l50.1,-530.2c-22.4,-7.6 -44.9,-12.7 -70.4,-12.7 -44,-0 -79.5,15.1 -120.4,32.7z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m1077.3,341.8 l-7,-35.5c-6.3,-31.8 -20.6,-61.9 -53.3,-73.5 -10.5,-3.7 -22.4,-5.3 -30.4,-12.9 -8,-7.6 -10.4,-19.5 -12.3,-30.4 -3.4,-20.1 -6.7,-40.3 -10.2,-60.4 -3,-17.3 -5.5,-36.7 -13.4,-52.6 -10.3,-21.3 -31.7,-33.8 -53,-42 -10.9,-4.1 -22.1,-7.5 -33.4,-10.3 -53.2,-14 -109.2,-19.2 -163.9,-22.1 -65.7,-3.6 -131.6,-2.5 -197.2,3.3 -48.8,4.4 -100.2,9.8 -146.6,26.7 -16.9,6.2 -34.4,13.6 -47.3,26.7 -15.8,16.1 -21,41 -9.4,61 8.2,14.2 22.1,24.3 36.9,31 19.2,8.6 39.3,15.1 59.8,19.5 57.3,12.7 116.6,17.6 175.2,19.8 64.9,2.6 129.9,0.5 194.4,-6.3 16,-1.8 31.9,-3.9 47.8,-6.3 18.7,-2.9 30.8,-27.4 25.2,-44.4 -6.6,-20.4 -24.4,-28.3 -44.4,-25.2 -3,0.5 -5.9,0.9 -8.9,1.3l-2.1,0.3c-6.8,0.9 -13.6,1.7 -20.4,2.4 -14.1,1.5 -28.1,2.8 -42.3,3.7 -31.6,2.2 -63.3,3.2 -95,3.3 -31.1,0 -62.3,-0.9 -93.4,-2.9 -14.2,-0.9 -28.3,-2.1 -42.4,-3.5 -6.4,-0.7 -12.8,-1.4 -19.2,-2.2l-6.1,-0.8 -1.3,-0.2 -6.3,-0.9c-12.9,-1.9 -25.8,-4.2 -38.6,-6.9 -1.3,-0.3 -2.4,-1 -3.3,-2 -0.8,-1 -1.3,-2.3 -1.3,-3.6 0,-1.3 0.4,-2.6 1.3,-3.6 0.8,-1 2,-1.7 3.3,-2h0.2c11.1,-2.4 22.2,-4.4 33.4,-6.1 3.7,-0.6 7.5,-1.2 11.2,-1.7h0.1c7,-0.5 14,-1.7 21,-2.5 60.6,-6.3 121.6,-8.5 182.5,-6.4 29.6,0.9 59.1,2.6 88.6,5.6 6.3,0.7 12.6,1.3 18.9,2.1 2.4,0.3 4.8,0.6 7.3,0.9l4.9,0.7c14.2,2.1 28.4,4.7 42.5,7.7 20.9,4.5 47.7,6 57,28.9 3,7.3 4.3,15.3 5.9,23l2.1,9.7c0.1,0.2 0.1,0.4 0.1,0.5 4.9,22.9 9.8,45.9 14.8,68.8 0.4,1.7 0.4,3.4 0,5.1 -0.3,1.7 -1,3.3 -2,4.7 -1,1.4 -2.3,2.6 -3.7,3.5 -1.5,0.9 -3.1,1.5 -4.8,1.7h-0.1l-3,0.4 -3,0.4c-9.4,1.2 -18.9,2.4 -28.3,3.4 -18.6,2.1 -37.3,4 -55.9,5.5 -37.1,3.1 -74.3,5.1 -111.6,6.1 -19,0.5 -38,0.7 -56.9,0.7 -75.5,-0.1 -151,-4.4 -226,-13.1 -8.1,-1 -16.2,-2 -24.4,-3 6.3,0.8 -4.6,-0.6 -6.8,-0.9 -5.2,-0.7 -10.3,-1.5 -15.5,-2.3 -17.3,-2.6 -34.5,-5.8 -51.8,-8.6 -20.9,-3.4 -40.9,-1.7 -59.8,8.6 -15.5,8.5 -28.1,21.5 -36,37.3 -8.2,16.9 -10.6,35.2 -14.2,53.3 -3.6,18.1 -9.3,37.6 -7.2,56.2 4.6,40.1 32.7,72.8 73.1,80.1 38,6.9 76.2,12.5 114.4,17.2 150.4,18.4 302.3,20.6 453.2,6.6 12.3,-1.1 24.6,-2.4 36.8,-3.8 3.8,-0.4 7.7,0 11.3,1.3 3.6,1.3 6.9,3.3 9.7,6 2.7,2.7 4.8,6 6.1,9.6 1.3,3.6 1.8,7.5 1.4,11.3l-3.8,37.1c-7.7,75 -15.4,150.1 -23.1,225.1 -8,78.8 -16.1,157.6 -24.2,236.3 -2.3,22.2 -4.6,44.4 -6.9,66.5 -2.2,21.8 -2.5,44.4 -6.7,65.9 -6.5,33.9 -29.5,54.8 -63,62.4 -30.7,7 -62.1,10.7 -93.6,10.9 -34.9,0.2 -69.8,-1.4 -104.7,-1.2 -37.3,0.2 -82.9,-3.2 -111.7,-31 -25.3,-24.4 -28.8,-62.5 -32.2,-95.5 -4.6,-43.7 -9.1,-87.3 -13.6,-131l-25.3,-242.8 -16.4,-157.1c-0.3,-2.6 -0.6,-5.2 -0.8,-7.8 -2,-18.7 -15.2,-37.1 -36.1,-36.1 -17.9,0.8 -38.2,16 -36.1,36.1l12.1,116.5 25.1,240.9c7.1,68.4 14.3,136.9 21.4,205.3 1.4,13.1 2.7,26.3 4.1,39.4 7.9,71.7 62.6,110.3 130.3,121.1 39.6,6.4 80.1,7.7 120.3,8.3 51.5,0.8 103.5,2.8 154.1,-6.5 75,-13.8 131.3,-63.9 139.4,-141.6 2.3,-22.4 4.6,-44.9 6.9,-67.3 7.6,-74.2 15.2,-148.5 22.9,-222.7l24.9,-242.6 11.4,-111.2c0.6,-5.5 2.9,-10.7 6.6,-14.8 3.7,-4.1 8.7,-6.9 14.1,-7.9 21.5,-4.2 42,-11.3 57.2,-27.7 24.3,-26 29.1,-59.9 20.5,-94.1zM998.5,383c-7.7,7.3 -19.3,10.7 -30.8,12.4 -128.7,19.1 -259.3,28.8 -389.4,24.5 -93.1,-3.2 -185.3,-13.5 -277.5,-26.5 -9,-1.3 -18.8,-2.9 -25,-9.6 -11.7,-12.6 -6,-37.9 -2.9,-53 2.8,-13.9 8.1,-32.4 24.7,-34.4 25.8,-3 55.8,7.9 81.3,11.7 30.7,4.7 61.6,8.4 92.6,11.3 132.2,12 266.6,10.2 398.2,-7.4 24,-3.2 47.9,-7 71.7,-11.2 21.2,-3.8 44.7,-10.9 57.6,11 8.8,15 10,35 8.6,51.9 -0.4,7.4 -3.6,14.3 -9,19.4z" />
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#e3e3e3"
|
||||
android:pathData="M440,720q-117,0 -198.5,-81.5T160,440v-240q0,-33 23.5,-56.5T240,120h500q58,0 99,41t41,99q0,58 -41,99t-99,41h-20v40q0,117 -81.5,198.5T440,720ZM240,320h400v-120L240,200v120ZM720,320h20q25,0 42.5,-17.5T800,260q0,-25 -17.5,-42.5T740,200h-20v120ZM200,840q-17,0 -28.5,-11.5T160,800q0,-17 11.5,-28.5T200,760h560q17,0 28.5,11.5T800,800q0,17 -11.5,28.5T760,840L200,840Z" />
|
||||
</vector>
|
||||
26
app/src/main/res/drawable/dnd.xml
Normal file
26
app/src/main/res/drawable/dnd.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#e3e3e3"
|
||||
android:pathData="M320,520h320q17,0 28.5,-11.5T680,480q0,-17 -11.5,-28.5T640,440L320,440q-17,0 -28.5,11.5T280,480q0,17 11.5,28.5T320,520ZM480,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>
|
||||
@@ -79,4 +79,6 @@
|
||||
<string name="appearance">Appearance</string>
|
||||
<string name="durations">Durations</string>
|
||||
<string name="sound">Sound</string>
|
||||
<string name="dnd">Do Not Disturb</string>
|
||||
<string name="dnd_desc">Turn on DND when running a Focus timer</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user