feat(notification): add option to show only current session progress
Closes: #162
This commit is contained in:
@@ -265,7 +265,7 @@ class TimerService : Service() {
|
|||||||
.setStyle(
|
.setStyle(
|
||||||
notificationStyle
|
notificationStyle
|
||||||
.setProgress( // Set the current progress by filling the previous intervals and part of the current interval
|
.setProgress( // Set the current progress by filling the previous intervals and part of the current interval
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA && !settingsState.singleProgressBar) {
|
||||||
(totalTime - remainingTime) + ((cycles + 1) / 2) * settingsState.focusTime.toInt() + (cycles / 2) * settingsState.shortBreakTime.toInt()
|
(totalTime - remainingTime) + ((cycles + 1) / 2) * settingsState.focusTime.toInt() + (cycles / 2) * settingsState.shortBreakTime.toInt()
|
||||||
} else (totalTime - remainingTime)
|
} else (totalTime - remainingTime)
|
||||||
)
|
)
|
||||||
@@ -288,7 +288,7 @@ class TimerService : Service() {
|
|||||||
notificationStyle = NotificationCompat.ProgressStyle()
|
notificationStyle = NotificationCompat.ProgressStyle()
|
||||||
.also {
|
.also {
|
||||||
// Add all the Focus, Short break and long break intervals in order
|
// Add all the Focus, Short break and long break intervals in order
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA && !settingsState.singleProgressBar) {
|
||||||
// Android 16 and later supports live updates
|
// Android 16 and later supports live updates
|
||||||
// Set progress bar sections if on Baklava or later
|
// Set progress bar sections if on Baklava or later
|
||||||
for (i in 0..<settingsState.sessionLength * 2) {
|
for (i in 0..<settingsState.sessionLength * 2) {
|
||||||
@@ -326,7 +326,6 @@ class TimerService : Service() {
|
|||||||
private suspend fun resetTimer() {
|
private suspend fun resetTimer() {
|
||||||
val settingsState = _settingsState.value
|
val settingsState = _settingsState.value
|
||||||
|
|
||||||
updateProgressSegments()
|
|
||||||
saveTimeToDb()
|
saveTimeToDb()
|
||||||
lastSavedDuration = 0
|
lastSavedDuration = 0
|
||||||
time = settingsState.focusTime
|
time = settingsState.focusTime
|
||||||
@@ -346,13 +345,13 @@ class TimerService : Service() {
|
|||||||
totalFocusCount = settingsState.sessionLength
|
totalFocusCount = settingsState.sessionLength
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateProgressSegments()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun skipTimer(fromButton: Boolean = false) {
|
private suspend fun skipTimer(fromButton: Boolean = false) {
|
||||||
val settingsState = _settingsState.value
|
val settingsState = _settingsState.value
|
||||||
updateProgressSegments()
|
|
||||||
saveTimeToDb()
|
saveTimeToDb()
|
||||||
updateProgressSegments()
|
|
||||||
showTimerNotification(0, paused = true, complete = !fromButton)
|
showTimerNotification(0, paused = true, complete = !fromButton)
|
||||||
lastSavedDuration = 0
|
lastSavedDuration = 0
|
||||||
startTime = 0L
|
startTime = 0L
|
||||||
@@ -394,6 +393,8 @@ class TimerService : Service() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateProgressSegments()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startAlarm() {
|
fun startAlarm() {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package org.nsh07.pomodoro.ui.settingsScreen.screens
|
|||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
@@ -117,33 +118,45 @@ fun TimerSettings(
|
|||||||
val notificationManagerService =
|
val notificationManagerService =
|
||||||
remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
|
remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
|
||||||
|
|
||||||
val switchItems = listOf(
|
val switchItems = remember(
|
||||||
SettingsSwitchItem(
|
settingsState.dndEnabled,
|
||||||
checked = settingsState.dndEnabled,
|
settingsState.aodEnabled,
|
||||||
enabled = !serviceRunning,
|
isPlus,
|
||||||
icon = R.drawable.dnd,
|
serviceRunning
|
||||||
label = R.string.dnd,
|
) {
|
||||||
description = R.string.dnd_desc,
|
listOf(
|
||||||
onClick = {
|
SettingsSwitchItem(
|
||||||
if (it && !notificationManagerService.isNotificationPolicyAccessGranted()) {
|
checked = settingsState.dndEnabled,
|
||||||
val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)
|
enabled = !serviceRunning,
|
||||||
Toast.makeText(context, "Enable permission for \"$appName\"", Toast.LENGTH_LONG)
|
icon = R.drawable.dnd,
|
||||||
.show()
|
label = R.string.dnd,
|
||||||
context.startActivity(intent)
|
description = R.string.dnd_desc,
|
||||||
} else if (!it && notificationManagerService.isNotificationPolicyAccessGranted()) {
|
onClick = {
|
||||||
notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL)
|
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)
|
||||||
|
}
|
||||||
|
onAction(SettingsAction.SaveDndEnabled(it))
|
||||||
}
|
}
|
||||||
onAction(SettingsAction.SaveDndEnabled(it))
|
),
|
||||||
}
|
SettingsSwitchItem(
|
||||||
),
|
checked = settingsState.aodEnabled,
|
||||||
SettingsSwitchItem(
|
enabled = isPlus,
|
||||||
checked = settingsState.aodEnabled,
|
icon = R.drawable.aod,
|
||||||
icon = R.drawable.aod,
|
label = R.string.always_on_display,
|
||||||
label = R.string.always_on_display,
|
description = R.string.always_on_display_desc,
|
||||||
description = R.string.always_on_display_desc,
|
onClick = { onAction(SettingsAction.SaveAodEnabled(it)) }
|
||||||
onClick = { onAction(SettingsAction.SaveAodEnabled(it)) }
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -343,6 +356,44 @@ fun TimerSettings(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
|
||||||
|
item { Spacer(Modifier.height(12.dp)) }
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
leadingContent = {
|
||||||
|
Icon(painterResource(R.drawable.view_day), null)
|
||||||
|
},
|
||||||
|
headlineContent = { Text(stringResource(R.string.session_only_progress)) },
|
||||||
|
supportingContent = { Text(stringResource(R.string.session_only_progress_desc)) },
|
||||||
|
trailingContent = {
|
||||||
|
Switch(
|
||||||
|
checked = settingsState.singleProgressBar,
|
||||||
|
enabled = !serviceRunning,
|
||||||
|
onCheckedChange = { onAction(SettingsAction.SaveSingleProgressBar(it)) },
|
||||||
|
thumbContent = {
|
||||||
|
if (settingsState.singleProgressBar) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.check),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(SwitchDefaults.IconSize),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.clear),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(SwitchDefaults.IconSize),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = switchColors
|
||||||
|
)
|
||||||
|
},
|
||||||
|
colors = listItemColors,
|
||||||
|
modifier = Modifier.clip(cardShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isPlus) {
|
if (!isPlus) {
|
||||||
item {
|
item {
|
||||||
PlusDivider(setShowPaywall)
|
PlusDivider(setShowPaywall)
|
||||||
@@ -362,7 +413,7 @@ fun TimerSettings(
|
|||||||
Switch(
|
Switch(
|
||||||
checked = item.checked,
|
checked = item.checked,
|
||||||
onCheckedChange = { item.onClick(it) },
|
onCheckedChange = { item.onClick(it) },
|
||||||
enabled = isPlus,
|
enabled = item.enabled,
|
||||||
thumbContent = {
|
thumbContent = {
|
||||||
if (item.checked) {
|
if (item.checked) {
|
||||||
Icon(
|
Icon(
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ sealed interface SettingsAction {
|
|||||||
data class SaveAodEnabled(val enabled: Boolean) : SettingsAction
|
data class SaveAodEnabled(val enabled: Boolean) : SettingsAction
|
||||||
data class SaveDndEnabled(val enabled: Boolean) : SettingsAction
|
data class SaveDndEnabled(val enabled: Boolean) : SettingsAction
|
||||||
data class SaveMediaVolumeForAlarm(val enabled: Boolean) : SettingsAction
|
data class SaveMediaVolumeForAlarm(val enabled: Boolean) : SettingsAction
|
||||||
|
data class SaveSingleProgressBar(val enabled: Boolean) : SettingsAction
|
||||||
data class SaveAlarmSound(val uri: Uri?) : SettingsAction
|
data class SaveAlarmSound(val uri: Uri?) : SettingsAction
|
||||||
data class SaveTheme(val theme: String) : SettingsAction
|
data class SaveTheme(val theme: String) : SettingsAction
|
||||||
data class SaveColorScheme(val color: Color) : SettingsAction
|
data class SaveColorScheme(val color: Color) : SettingsAction
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ data class SettingsState(
|
|||||||
val vibrateEnabled: Boolean = true,
|
val vibrateEnabled: Boolean = true,
|
||||||
val dndEnabled: Boolean = false,
|
val dndEnabled: Boolean = false,
|
||||||
val mediaVolumeForAlarm: Boolean = false,
|
val mediaVolumeForAlarm: Boolean = false,
|
||||||
|
val singleProgressBar: Boolean = false,
|
||||||
|
|
||||||
val focusTime: Long = 25 * 60 * 1000L,
|
val focusTime: Long = 25 * 60 * 1000L,
|
||||||
val shortBreakTime: Long = 5 * 60 * 1000L,
|
val shortBreakTime: Long = 5 * 60 * 1000L,
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ class SettingsViewModel(
|
|||||||
is SettingsAction.SaveVibrateEnabled -> saveVibrateEnabled(action.enabled)
|
is SettingsAction.SaveVibrateEnabled -> saveVibrateEnabled(action.enabled)
|
||||||
is SettingsAction.SaveDndEnabled -> saveDndEnabled(action.enabled)
|
is SettingsAction.SaveDndEnabled -> saveDndEnabled(action.enabled)
|
||||||
is SettingsAction.SaveMediaVolumeForAlarm -> saveMediaVolumeForAlarm(action.enabled)
|
is SettingsAction.SaveMediaVolumeForAlarm -> saveMediaVolumeForAlarm(action.enabled)
|
||||||
|
is SettingsAction.SaveSingleProgressBar -> saveSingleProgressBar(action.enabled)
|
||||||
is SettingsAction.SaveColorScheme -> saveColorScheme(action.color)
|
is SettingsAction.SaveColorScheme -> saveColorScheme(action.color)
|
||||||
is SettingsAction.SaveTheme -> saveTheme(action.theme)
|
is SettingsAction.SaveTheme -> saveTheme(action.theme)
|
||||||
is SettingsAction.SaveBlackTheme -> saveBlackTheme(action.enabled)
|
is SettingsAction.SaveBlackTheme -> saveBlackTheme(action.enabled)
|
||||||
@@ -273,6 +274,18 @@ class SettingsViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun saveSingleProgressBar(singleProgressBar: Boolean) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_settingsState.update { currentState ->
|
||||||
|
currentState.copy(singleProgressBar = singleProgressBar)
|
||||||
|
}
|
||||||
|
preferenceRepository.saveBooleanPreference(
|
||||||
|
"single_progress_bar",
|
||||||
|
singleProgressBar
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun reloadSettings() {
|
suspend fun reloadSettings() {
|
||||||
var settingsState = _settingsState.value
|
var settingsState = _settingsState.value
|
||||||
val focusTime =
|
val focusTime =
|
||||||
@@ -335,6 +348,11 @@ class SettingsViewModel(
|
|||||||
"media_volume_for_alarm",
|
"media_volume_for_alarm",
|
||||||
settingsState.mediaVolumeForAlarm
|
settingsState.mediaVolumeForAlarm
|
||||||
)
|
)
|
||||||
|
val singleProgressBar = preferenceRepository.getBooleanPreference("single_progress_bar")
|
||||||
|
?: preferenceRepository.saveBooleanPreference(
|
||||||
|
"single_progress_bar",
|
||||||
|
settingsState.singleProgressBar
|
||||||
|
)
|
||||||
|
|
||||||
_settingsState.update { currentState ->
|
_settingsState.update { currentState ->
|
||||||
currentState.copy(
|
currentState.copy(
|
||||||
@@ -350,7 +368,8 @@ class SettingsViewModel(
|
|||||||
alarmEnabled = alarmEnabled,
|
alarmEnabled = alarmEnabled,
|
||||||
vibrateEnabled = vibrateEnabled,
|
vibrateEnabled = vibrateEnabled,
|
||||||
dndEnabled = dndEnabled,
|
dndEnabled = dndEnabled,
|
||||||
mediaVolumeForAlarm = mediaVolumeForAlarm
|
mediaVolumeForAlarm = mediaVolumeForAlarm,
|
||||||
|
singleProgressBar = singleProgressBar
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
26
app/src/main/res/drawable/view_day.xml
Normal file
26
app/src/main/res/drawable/view_day.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="M159,800q-17,0 -28,-11.5T120,760q0,-17 11.5,-28.5T160,720h641q17,0 28,11.5t11,28.5q0,17 -11.5,28.5T800,800L159,800ZM200,640q-33,0 -56.5,-23.5T120,560v-160q0,-33 23.5,-56.5T200,320h560q33,0 56.5,23.5T840,400v160q0,33 -23.5,56.5T760,640L200,640ZM159,240q-17,0 -28,-11.5T120,200q0,-17 11.5,-28.5T160,160h641q17,0 28,11.5t11,28.5q0,17 -11.5,28.5T800,240L159,240Z" />
|
||||||
|
</vector>
|
||||||
@@ -105,4 +105,6 @@
|
|||||||
<string name="license">License</string>
|
<string name="license">License</string>
|
||||||
<string name="media_volume_for_alarm">Headphone mode</string>
|
<string name="media_volume_for_alarm">Headphone mode</string>
|
||||||
<string name="media_volume_for_alarm_desc">Plays on headphones only. If headphones are disconnected, alarm plays through speaker at media volume.</string>
|
<string name="media_volume_for_alarm_desc">Plays on headphones only. If headphones are disconnected, alarm plays through speaker at media volume.</string>
|
||||||
|
<string name="session_only_progress">Session-only progress</string>
|
||||||
|
<string name="session_only_progress_desc">Show progress for the current session only in notifications, rather than the full sequence.</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user