From 8e8e4ddf3729e81f867eb6c592ad12408eb2c673 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Fri, 5 Dec 2025 04:37:37 +0100 Subject: [PATCH 01/20] Added translation using Weblate (Telugu) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translated using Weblate (Arabic) Currently translated at 100.0% (91 of 91 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (91 of 91 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (91 of 91 strings) Translated using Weblate (French) Currently translated at 96.7% (88 of 91 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (91 of 91 strings) Translated using Weblate (German) Currently translated at 100.0% (91 of 91 strings) Co-authored-by: AhmedAwad Co-authored-by: Damien Navarro Co-authored-by: Erenay Co-authored-by: Feuerstern Co-authored-by: Hosted Weblate Co-authored-by: Pavan Kumar Chava Co-authored-by: vintagezero Co-authored-by: 大王叫我来巡山 Translate-URL: https://hosted.weblate.org/projects/tomato/strings/ar/ Translate-URL: https://hosted.weblate.org/projects/tomato/strings/de/ Translate-URL: https://hosted.weblate.org/projects/tomato/strings/fr/ Translate-URL: https://hosted.weblate.org/projects/tomato/strings/tr/ Translate-URL: https://hosted.weblate.org/projects/tomato/strings/uk/ Translate-URL: https://hosted.weblate.org/projects/tomato/strings/zh_Hans/ Translation: Tomato/Strings --- app/src/main/res/values-ar/strings.xml | 15 ++++++++++++++- app/src/main/res/values-de/strings.xml | 15 ++++++++++++++- app/src/main/res/values-fr/strings.xml | 7 ++++++- app/src/main/res/values-te/strings.xml | 3 +++ app/src/main/res/values-tr/strings.xml | 10 +++++++--- app/src/main/res/values-uk/strings.xml | 11 ++++++++--- app/src/main/res/values-zh-rCN/strings.xml | 4 ++++ 7 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 app/src/main/res/values-te/strings.xml diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 49c7c6c..2db556d 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -74,5 +74,18 @@ قيم التطبيق علي جوجل بلاي مُختار أعد المؤقت لتغير الاعدادات - المساعدة في الترجمة + المساعدة في ترجمة Tomato + %1$dس %2$dد + %1$dس + %1$dد + عن + ترجم Tomato+ إلى لغتك + هل أعجبك التطبيق؟ اكتب مراجعة! + ادعمني بتبرع صغير + يمكنك التخصيص أكثر باستخدام Tomato+ + الترخيص + وضع سماعات الرأس + يعمل فقط على سماعات الرأس. إذا كانت سماعات الرأس مفصولة، يصدر المنبه عبر سماعة الهاتف عند مستوى صوت الوسائط. + التقدم في الجلسة فقط + عرض التقدم للجلسة الحالية فقط في الإشعارات، وليس التسلسل الكامل. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 147c97c..ed12735 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -73,6 +73,19 @@ Sprache wählen Im Play Store bewerten Ausgewählt - Hilf beim Übersetzen + Hilf beim Übersetzen von Tomato Den Timer zurücksetzen um Einstellungen zu ändern + %1$dh%2$ddm + %1$dh + %1$dm + Über + Übersetze Tomato in deine Sprache + Dir gefällt die App? Schreibe eine Bewertung! + Unterstütze mich mit einer kleinen Spende + Weiter anpassen mit Tomato+ + Lizenz + Kopfhörer Modus + Spielt nur auf Kopfhörern. Wenn die Kopfhörer getrennt sind, wieder Alarm in Medienlautstärke abgespielt. + Sitzungs-Fortschritt + Zeige in den Benachrichtigungen nur den Fortschritt der aktuellen Sitzung an, nicht die gesamte Sequenz. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8e4529b..566622e 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -80,5 +80,10 @@ Personnalisez plus avec Tomato+ A propos Traduire Tomato dans votre langue - Vous aimez l\'appli? Écrivez un avis! + Vous avez aimé l\'appli? Écrivez un avis! + Licence + Mode casque + Progression de la session uniquement + Montre uniquement la progression de la session actuelle, au lieu de la séquence entière. + Fonctionne uniquement avec un casque. Si le casque est déconnecté, l\'alarme retentit via le haut-parleur au volume des médias. diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml new file mode 100644 index 0000000..55344e5 --- /dev/null +++ b/app/src/main/res/values-te/strings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index c297dbc..ab188e3 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -75,13 +75,17 @@ Seçilen Tomato\'nun çevirisine yardım edin Ayarları değiştirmek için zamanlayıcıyı sıfırlayın - %dsa %ddk - %dsa - %ddk + %1$dsa %2$ddk + %1$dsa + %1$ddk Hakkında Tomato\'yu kendi dilinize çevirin Uygulamayı beğendinizmi? Bir yorum bırakın! Beni küçük bir bağışla destekleyin Tomato+ ile daha fazla özelleştirin Lisans + Kulaklık modu + Sadece kulaklıktan oynatır. Eğer kulaklık devre dışı ise alarmı direkt medya sesi düzeyinde hoparlörden oynatır. + Yalnızca bu oturumun ilerlemesi + Bildirimlerde tüm ilerlemeyi göstermektense yalnızca bu oturuma ait ilerlemeyi gösterir. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 5b34487..8f57266 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -76,12 +76,17 @@ Обрано Перезапустіть таймер, щоб змінити налаштування Допомогти з перекладом Tomato - %dгод %dхв - %dгод - %dхв + %1$dгод %2$dхв + %1$dгод + %1$dхв Відомості Перекладіть Tomato на Вашу мову Сподобався додаток? Напишіть відгук! Підтримайте мене невеликим пожертвуванням Налаштуйте ще більше з Tomato+ + Ліцензія + Режим навушників + Сигнал грає лише в навушниках. Якщо навушники від\'єднані, сигнал грає через динамік на гучності звуку медіа. + Прогрес лише поточної сесії + Показувати прогрес у сповіщенні лише для поточної сесії, а не для усього порядку. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a5fcf61..c9b77da 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -84,4 +84,8 @@ 小额捐赠支持 用 Tomato+ 进一步定制 许可证 + 耳机模式 + 仅在插入耳机时播放。如果耳机连接断开,闹铃以媒体音量通过扬声器播放。 + 仅会话进度 + 在通知中只显示当前会话的进度,而非完整序列的进度。 From e5b30b121d534cfbd3c272b5c2329baf0f844dfc Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 6 Dec 2025 13:00:21 +0100 Subject: [PATCH 02/20] Translated using Weblate (Japanese) Currently translated at 95.6% (87 of 91 strings) Co-authored-by: S-H-Y-A Translate-URL: https://hosted.weblate.org/projects/tomato/strings/ja/ Translation: Tomato/Strings --- app/src/main/res/values-ja/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 73aa1bb..f0b8292 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -74,10 +74,10 @@ タイマー表示中に常時表示ディスプレイに切り替えるには任意の場所をタップしてください タイマーの実行時にサイレントモードをオンにします 全機能がこのバージョンでは解放されています。私のアプリが生活に変化をもたらしたのであれば、%1$s で寄付することで私を支援することをご検討ください。 - %d時%d分 - %d時 - %d分 - について + %1$d時%2$d分 + %1$d時 + %1$d分 + このアプリについて Tomato をあなたの言語に翻訳してください アプリが気に入りましたか?レビューを書いてください! 小さな寄付でご支援してもよろしいでしょうか From c0dc45d740dd8d010006370512aaa95aae2f45f3 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Sat, 6 Dec 2025 20:37:46 +0530 Subject: [PATCH 03/20] feat(ui): use a lighter font weight for AOD --- app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt b/app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt index e3cc1ea..10f3ea3 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt @@ -63,7 +63,7 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.navigation3.ui.LocalNavAnimatedContentScope import kotlinx.coroutines.delay -import org.nsh07.pomodoro.ui.theme.AppFonts.googleFlex600 +import org.nsh07.pomodoro.ui.theme.AppFonts.googleFlex400 import org.nsh07.pomodoro.ui.theme.TomatoTheme import org.nsh07.pomodoro.ui.timerScreen.TimerScreen import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerMode @@ -246,7 +246,7 @@ fun SharedTransitionScope.AlwaysOnDisplay( Text( text = timerState.timeStr, style = TextStyle( - fontFamily = googleFlex600, + fontFamily = googleFlex400, fontSize = 56.sp, letterSpacing = (-2).sp, fontFeatureSettings = "tnum" From b8630ce63915b5263d01bba2da2c5e07b12ce9e5 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Sat, 6 Dec 2025 20:44:33 +0530 Subject: [PATCH 04/20] fix(stats): perform flow transformations on Dispatchers.IO --- .../viewModel/SettingsViewModel.kt | 5 ++++- .../ui/statsScreen/viewModel/StatsViewModel.kt | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) 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 5a524c2..0be8e02 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 @@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update @@ -64,7 +65,9 @@ class SettingsViewModel( val backStack = mutableStateListOf(Screen.Settings.Main) val isPlus = billingManager.isPlus - val serviceRunning = stateRepository.timerState.map { it.serviceRunning } + val serviceRunning = stateRepository.timerState + .map { it.serviceRunning } + .flowOn(Dispatchers.IO) .stateIn( viewModelScope, SharingStarted.WhileSubscribed(5000), diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt index e5cb9f1..e5b6cdd 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt @@ -27,9 +27,10 @@ import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer import com.patrykandpatrick.vico.core.cartesian.data.columnSeries import com.patrykandpatrick.vico.core.cartesian.data.lineSeries import com.patrykandpatrick.vico.core.common.data.ExtraStore +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -46,7 +47,13 @@ class StatsViewModel( private val statRepository: StatRepository ) : ViewModel() { - val todayStat = statRepository.getTodayStat().distinctUntilChanged() + val todayStat = statRepository + .getTodayStat() + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = null + ) private val lastWeekSummary = Pair(CartesianChartModelProducer(), ExtraStore.Key>()) @@ -75,6 +82,7 @@ class StatsViewModel( } lastWeekSummary } + .flowOn(Dispatchers.IO) .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), @@ -91,6 +99,7 @@ class StatsViewModel( it?.focusTimeQ4?.toInt() ?: 0 ) } + .flowOn(Dispatchers.IO) .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), @@ -109,6 +118,7 @@ class StatsViewModel( } lastMonthSummary } + .flowOn(Dispatchers.IO) .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), @@ -125,6 +135,7 @@ class StatsViewModel( it?.focusTimeQ4?.toInt() ?: 0 ) } + .flowOn(Dispatchers.IO) .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), @@ -143,6 +154,7 @@ class StatsViewModel( } lastYearSummary } + .flowOn(Dispatchers.IO) .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), @@ -159,6 +171,7 @@ class StatsViewModel( it?.focusTimeQ4?.toInt() ?: 0 ) } + .flowOn(Dispatchers.IO) .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), From c7dd860e260979cb73570232b5fbd6edd3e4d8a0 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Sat, 6 Dec 2025 20:47:57 +0530 Subject: [PATCH 05/20] fix(ui): fix incorrect toolbar text alignment Closes: #164 --- app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt index 7305806..60c528b 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt @@ -212,7 +212,7 @@ fun AppScreen( ), modifier = Modifier.height(56.dp) ) { - Row { + Row(verticalAlignment = Alignment.CenterVertically) { Crossfade(selected) { if (it) Icon( painterResource(item.selectedIcon), From 960174a05be789deb03bbc65e5c500430520aefe Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Sat, 6 Dec 2025 22:23:28 +0530 Subject: [PATCH 06/20] fix(ui): make a settings option collapsible in timer settings --- .../ui/settingsScreen/screens/TimerSettings.kt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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 d68aba9..4ea0853 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 @@ -24,7 +24,9 @@ import android.os.Build import android.provider.Settings import android.widget.Toast import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -54,6 +56,7 @@ import androidx.compose.material3.LargeFlexibleTopAppBar import androidx.compose.material3.ListItem import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.motionScheme import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider @@ -77,6 +80,7 @@ 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 +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.nsh07.pomodoro.R @@ -364,7 +368,17 @@ fun TimerSettings( Icon(painterResource(R.drawable.view_day), null) }, headlineContent = { Text(stringResource(R.string.session_only_progress)) }, - supportingContent = { Text(stringResource(R.string.session_only_progress_desc)) }, + supportingContent = { + var expanded by remember { mutableStateOf(false) } + Text( + stringResource(R.string.session_only_progress_desc), + maxLines = if (expanded) Int.MAX_VALUE else 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .clickable { expanded = !expanded } + .animateContentSize(motionScheme.defaultSpatialSpec()) + ) + }, trailingContent = { Switch( checked = settingsState.singleProgressBar, From 225b06d0ef23be4117eda85b06fb2d72db04b17b Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Sat, 6 Dec 2025 22:25:14 +0530 Subject: [PATCH 07/20] fix(ui): use default list item color for palette icon in color scheme --- .../ui/settingsScreen/components/ColorSchemePickerListItem.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ColorSchemePickerListItem.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ColorSchemePickerListItem.kt index 095ffec..9da412c 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ColorSchemePickerListItem.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ColorSchemePickerListItem.kt @@ -35,7 +35,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.ListItem -import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text @@ -128,8 +127,7 @@ fun ColorSchemePickerListItem( leadingContent = { Icon( painter = painterResource(R.drawable.palette), - contentDescription = null, - tint = colorScheme.primary + contentDescription = null ) }, headlineContent = { Text(stringResource(R.string.color_scheme)) }, From 1b18767c1e0648a2c3d80c04fbf32308988f4433 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 8 Dec 2025 14:00:22 +0100 Subject: [PATCH 08/20] Translated using Weblate (Japanese) Currently translated at 97.8% (89 of 91 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (91 of 91 strings) Co-authored-by: K-MAX Co-authored-by: daiman Translate-URL: https://hosted.weblate.org/projects/tomato/strings/ar/ Translate-URL: https://hosted.weblate.org/projects/tomato/strings/ja/ Translation: Tomato/Strings --- app/src/main/res/values-ar/strings.xml | 6 +++--- app/src/main/res/values-ja/strings.xml | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 2db556d..4038ef2 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -79,13 +79,13 @@ %1$dس %1$dد عن - ترجم Tomato+ إلى لغتك + ترجم Tomato إلى لغتك هل أعجبك التطبيق؟ اكتب مراجعة! ادعمني بتبرع صغير - يمكنك التخصيص أكثر باستخدام Tomato+ + التخصيص أكثر باستخدام Tomato+ الترخيص وضع سماعات الرأس يعمل فقط على سماعات الرأس. إذا كانت سماعات الرأس مفصولة، يصدر المنبه عبر سماعة الهاتف عند مستوى صوت الوسائط. التقدم في الجلسة فقط - عرض التقدم للجلسة الحالية فقط في الإشعارات، وليس التسلسل الكامل. + عرض التقدم للجلسة الحالية بدلا من سلسلة الجلسات كلها diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index f0b8292..86998c6 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -83,4 +83,6 @@ 小さな寄付でご支援してもよろしいでしょうか Tomato+ を使用してさらにカスタマイズする ライセンス + ヘッドホンモード + ヘッドホンで再生します。ヘッドホンが接続されていない場合はスピーカーからメディアボリュームで再生されます。 From a48918351dfd8ed07947a6a10d9112be153669ca Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Mon, 8 Dec 2025 18:40:13 +0530 Subject: [PATCH 09/20] chore(github): update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 12 +++++++----- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 068693d..7b07dec 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report of a bug or crash title: "[BUG] " -labels: bug, priority-unassigned +labels: bug, needs-triage assignees: nsh07 --- @@ -12,6 +12,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -24,10 +25,11 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Device (please complete the following information):** - - Device model: [e.g. Google Pixel 10] - - Android version: [e.g. Android 16] - - App version: [e.g. v1.4.3] - - Installed from: [e.g. Play Store] + +- Device model: [e.g. Google Pixel 10] +- Android version: [e.g. Android 16] +- App version: [e.g. v1.4.3] +- Installed from: [e.g. Play Store] **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index b69c265..e26c687 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,7 @@ name: Feature request about: Suggest a new feature title: "[FEATURE] " -labels: enhancement, priority-unassigned +labels: enhancement, needs-triage assignees: nsh07 --- From fe85bc30d44bbfcd0aa574a6f05527dfd1c3d5c6 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Mon, 8 Dec 2025 19:19:09 +0530 Subject: [PATCH 10/20] feat(settings): add settings option to auto start sessions #108 --- .../settingsScreen/screens/TimerSettings.kt | 32 +++++++++++++++---- .../viewModel/SettingsAction.kt | 1 + .../settingsScreen/viewModel/SettingsState.kt | 1 + .../viewModel/SettingsViewModel.kt | 22 ++++++++++++- app/src/main/res/drawable/autoplay.xml | 26 +++++++++++++++ app/src/main/res/values/strings.xml | 2 ++ 6 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 app/src/main/res/drawable/autoplay.xml 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 4ea0853..d74f2aa 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 @@ -77,6 +77,7 @@ 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.platform.LocalInspectionMode import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction @@ -118,17 +119,30 @@ fun TimerSettings( ) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val context = LocalContext.current + val inspectionMode = LocalInspectionMode.current val appName = stringResource(R.string.app_name) - val notificationManagerService = - remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } + val notificationManagerService = remember { + if (!inspectionMode) + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + else + null + } val switchItems = remember( settingsState.dndEnabled, settingsState.aodEnabled, + settingsState.autostartNextSession, isPlus, serviceRunning ) { listOf( + SettingsSwitchItem( + checked = settingsState.autostartNextSession, + icon = R.drawable.autoplay, + label = R.string.auto_start_next_session, + description = R.string.auto_start_next_session_desc, + onClick = { onAction(SettingsAction.SaveAutostartNextSession(it)) } + ), SettingsSwitchItem( checked = settingsState.dndEnabled, enabled = !serviceRunning, @@ -136,7 +150,7 @@ fun TimerSettings( label = R.string.dnd, description = R.string.dnd_desc, onClick = { - if (it && !notificationManagerService.isNotificationPolicyAccessGranted()) { + if (it && notificationManagerService?.isNotificationPolicyAccessGranted() == false) { val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS) Toast.makeText( context, @@ -145,7 +159,7 @@ fun TimerSettings( ) .show() context.startActivity(intent) - } else if (!it && notificationManagerService.isNotificationPolicyAccessGranted()) { + } else if (!it && notificationManagerService?.isNotificationPolicyAccessGranted() == true) { notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL) } onAction(SettingsAction.SaveDndEnabled(it)) @@ -314,7 +328,7 @@ fun TimerSettings( } item { Spacer(Modifier.height(12.dp)) } - itemsIndexed(if (isPlus) switchItems else switchItems.take(1)) { index, item -> + itemsIndexed(if (isPlus) switchItems else switchItems.take(2)) { index, item -> ListItem( leadingContent = { Icon( @@ -355,7 +369,11 @@ fun TimerSettings( switchItems.size - 1 -> bottomListItemShape else -> middleListItemShape } - else cardShape + else when (index) { + 0 -> topListItemShape + switchItems.size - 2 -> bottomListItemShape + else -> middleListItemShape + } ) ) } @@ -501,7 +519,7 @@ private fun TimerSettingsPreview() { ) TimerSettings( isPlus = false, - serviceRunning = true, + serviceRunning = false, settingsState = remember { SettingsState() }, contentPadding = PaddingValues(), focusTimeInputFieldState = focusTimeInputFieldState, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt index 9603809..34f244c 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt @@ -28,6 +28,7 @@ sealed interface SettingsAction { data class SaveDndEnabled(val enabled: Boolean) : SettingsAction data class SaveMediaVolumeForAlarm(val enabled: Boolean) : SettingsAction data class SaveSingleProgressBar(val enabled: Boolean) : SettingsAction + data class SaveAutostartNextSession(val enabled: Boolean) : SettingsAction data class SaveAlarmSound(val uri: Uri?) : SettingsAction data class SaveTheme(val theme: String) : SettingsAction data class SaveColorScheme(val color: Color) : SettingsAction diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt index 1ca0165..c839866 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt @@ -33,6 +33,7 @@ data class SettingsState( val dndEnabled: Boolean = false, val mediaVolumeForAlarm: Boolean = false, val singleProgressBar: Boolean = false, + val autostartNextSession: Boolean = false, val focusTime: Long = 25 * 60 * 1000L, val shortBreakTime: Long = 5 * 60 * 1000L, 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 0be8e02..aac7009 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 @@ -114,6 +114,7 @@ class SettingsViewModel( is SettingsAction.SaveDndEnabled -> saveDndEnabled(action.enabled) is SettingsAction.SaveMediaVolumeForAlarm -> saveMediaVolumeForAlarm(action.enabled) is SettingsAction.SaveSingleProgressBar -> saveSingleProgressBar(action.enabled) + is SettingsAction.SaveAutostartNextSession -> saveAutostartNextSession(action.enabled) is SettingsAction.SaveColorScheme -> saveColorScheme(action.color) is SettingsAction.SaveTheme -> saveTheme(action.theme) is SettingsAction.SaveBlackTheme -> saveBlackTheme(action.enabled) @@ -289,6 +290,18 @@ class SettingsViewModel( } } + private fun saveAutostartNextSession(autostartNextSession: Boolean) { + viewModelScope.launch { + _settingsState.update { currentState -> + currentState.copy(autostartNextSession = autostartNextSession) + } + preferenceRepository.saveBooleanPreference( + "autostart_next_session", + autostartNextSession + ) + } + } + suspend fun reloadSettings() { var settingsState = _settingsState.value val focusTime = @@ -356,6 +369,12 @@ class SettingsViewModel( "single_progress_bar", settingsState.singleProgressBar ) + val autostartNextSession = + preferenceRepository.getBooleanPreference("autostart_next_session") + ?: preferenceRepository.saveBooleanPreference( + "autostart_next_session", + settingsState.autostartNextSession + ) _settingsState.update { currentState -> currentState.copy( @@ -372,7 +391,8 @@ class SettingsViewModel( vibrateEnabled = vibrateEnabled, dndEnabled = dndEnabled, mediaVolumeForAlarm = mediaVolumeForAlarm, - singleProgressBar = singleProgressBar + singleProgressBar = singleProgressBar, + autostartNextSession = autostartNextSession ) } diff --git a/app/src/main/res/drawable/autoplay.xml b/app/src/main/res/drawable/autoplay.xml new file mode 100644 index 0000000..c4920b6 --- /dev/null +++ b/app/src/main/res/drawable/autoplay.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0cd5947..0d2b3dd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -107,4 +107,6 @@ Plays on headphones only. If headphones are disconnected, alarm plays through speaker at media volume. Session-only progress Show progress for the current session only in notifications, rather than the full sequence. + Start next session after stopping an alarm + Auto start next session \ No newline at end of file From a32aae0692dfd3e9259ba6b319a94e8c5e11c475 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Mon, 8 Dec 2025 19:26:26 +0530 Subject: [PATCH 11/20] feat(service): implement auto start behaviour Closes: #108 --- app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt | 2 ++ 1 file changed, 2 insertions(+) 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 b369d57..892d5ac 100644 --- a/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt +++ b/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt @@ -449,6 +449,8 @@ class TimerService : Service() { else -> settingsState.longBreakTime.toInt() }, paused = true, complete = false ) + + if (settingsState.autostartNextSession) toggleTimer() } private fun initializeMediaPlayer(): MediaPlayer? { From db23a460aa70f7fd30d59045c08b0a8b53404115 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Tue, 9 Dec 2025 09:57:54 +0530 Subject: [PATCH 12/20] feat(settings): add settings option to disable screen lock in AOD #166 --- .../settingsScreen/screens/TimerSettings.kt | 151 ++++++++++++------ .../viewModel/SettingsAction.kt | 1 + .../settingsScreen/viewModel/SettingsState.kt | 1 + .../viewModel/SettingsViewModel.kt | 18 ++- .../res/drawable/mobile_lock_portrait.xml | 26 +++ app/src/main/res/values/strings.xml | 10 +- 6 files changed, 157 insertions(+), 50 deletions(-) create mode 100644 app/src/main/res/drawable/mobile_lock_portrait.xml 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 d74f2aa..5aad189 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 @@ -40,7 +40,6 @@ 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.items import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -132,46 +131,59 @@ fun TimerSettings( settingsState.dndEnabled, settingsState.aodEnabled, settingsState.autostartNextSession, + settingsState.lockScreenInAod, isPlus, serviceRunning ) { listOf( - SettingsSwitchItem( - checked = settingsState.autostartNextSession, - icon = R.drawable.autoplay, - label = R.string.auto_start_next_session, - description = R.string.auto_start_next_session_desc, - onClick = { onAction(SettingsAction.SaveAutostartNextSession(it)) } - ), - SettingsSwitchItem( - checked = settingsState.dndEnabled, - enabled = !serviceRunning, - icon = R.drawable.dnd, - label = R.string.dnd, - description = R.string.dnd_desc, - onClick = { - if (it && notificationManagerService?.isNotificationPolicyAccessGranted() == false) { - 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() == true) { - notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL) + listOf( + SettingsSwitchItem( + checked = settingsState.autostartNextSession, + icon = R.drawable.autoplay, + label = R.string.auto_start_next_timer, + description = R.string.auto_start_next_timer_desc, + onClick = { onAction(SettingsAction.SaveAutostartNextSession(it)) } + ), + SettingsSwitchItem( + checked = settingsState.dndEnabled, + enabled = !serviceRunning, + icon = R.drawable.dnd, + label = R.string.dnd, + description = R.string.dnd_desc, + onClick = { + if (it && notificationManagerService?.isNotificationPolicyAccessGranted() == false) { + 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() == true) { + notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL) + } + onAction(SettingsAction.SaveDndEnabled(it)) } - onAction(SettingsAction.SaveDndEnabled(it)) - } + ) ), - SettingsSwitchItem( - checked = settingsState.aodEnabled, - enabled = isPlus, - icon = R.drawable.aod, - label = R.string.always_on_display, - description = R.string.always_on_display_desc, - onClick = { onAction(SettingsAction.SaveAodEnabled(it)) } + listOf( + SettingsSwitchItem( + checked = settingsState.aodEnabled, + enabled = isPlus, + icon = R.drawable.aod, + label = R.string.always_on_display, + description = R.string.always_on_display_desc, + onClick = { onAction(SettingsAction.SaveAodEnabled(it)) } + ), + SettingsSwitchItem( + checked = settingsState.lockScreenInAod && isPlus, + enabled = isPlus, + icon = R.drawable.mobile_lock_portrait, + label = R.string.secure_aod, + description = R.string.secure_aod_desc, + onClick = { onAction(SettingsAction.SaveLockScreenInAod(it)) } + ) ) ) } @@ -328,7 +340,7 @@ fun TimerSettings( } item { Spacer(Modifier.height(12.dp)) } - itemsIndexed(if (isPlus) switchItems else switchItems.take(2)) { index, item -> + itemsIndexed(switchItems[0]) { index, item -> ListItem( leadingContent = { Icon( @@ -364,20 +376,63 @@ fun TimerSettings( }, colors = listItemColors, modifier = Modifier.clip( - if (isPlus) when (index) { + when (index) { 0 -> topListItemShape - switchItems.size - 1 -> bottomListItemShape - else -> middleListItemShape - } - else when (index) { - 0 -> topListItemShape - switchItems.size - 2 -> bottomListItemShape + switchItems[0].size - 1 -> bottomListItemShape else -> middleListItemShape } ) ) } + if (isPlus) { + item { Spacer(Modifier.height(12.dp)) } + itemsIndexed(switchItems[1]) { index, item -> + ListItem( + leadingContent = { + Icon( + painterResource(item.icon), + contentDescription = null, + modifier = Modifier.padding(top = 4.dp) + ) + }, + headlineContent = { Text(stringResource(item.label)) }, + supportingContent = { Text(stringResource(item.description)) }, + trailingContent = { + Switch( + checked = item.checked, + onCheckedChange = { item.onClick(it) }, + enabled = item.enabled, + thumbContent = { + if (item.checked) { + 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( + when (index) { + 0 -> topListItemShape + switchItems[1].size - 1 -> bottomListItemShape + else -> middleListItemShape + } + ) + ) + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) { item { Spacer(Modifier.height(12.dp)) } item { @@ -430,7 +485,7 @@ fun TimerSettings( item { PlusDivider(setShowPaywall) } - items(switchItems.drop(1)) { item -> + itemsIndexed(switchItems[1]) { index, item -> ListItem( leadingContent = { Icon( @@ -465,7 +520,13 @@ fun TimerSettings( ) }, colors = listItemColors, - modifier = Modifier.clip(cardShape) + modifier = Modifier.clip( + when (index) { + 0 -> topListItemShape + switchItems[1].size - 1 -> bottomListItemShape + else -> middleListItemShape + } + ) ) } } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt index 34f244c..b9cf69b 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt @@ -29,6 +29,7 @@ sealed interface SettingsAction { data class SaveMediaVolumeForAlarm(val enabled: Boolean) : SettingsAction data class SaveSingleProgressBar(val enabled: Boolean) : SettingsAction data class SaveAutostartNextSession(val enabled: Boolean) : SettingsAction + data class SaveLockScreenInAod(val enabled: Boolean) : SettingsAction data class SaveAlarmSound(val uri: Uri?) : SettingsAction data class SaveTheme(val theme: String) : SettingsAction data class SaveColorScheme(val color: Color) : SettingsAction diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt index c839866..1ecdd23 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt @@ -34,6 +34,7 @@ data class SettingsState( val mediaVolumeForAlarm: Boolean = false, val singleProgressBar: Boolean = false, val autostartNextSession: Boolean = false, + val lockScreenInAod: Boolean = true, val focusTime: Long = 25 * 60 * 1000L, val shortBreakTime: Long = 5 * 60 * 1000L, 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 aac7009..2ab6250 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 @@ -115,6 +115,7 @@ class SettingsViewModel( is SettingsAction.SaveMediaVolumeForAlarm -> saveMediaVolumeForAlarm(action.enabled) is SettingsAction.SaveSingleProgressBar -> saveSingleProgressBar(action.enabled) is SettingsAction.SaveAutostartNextSession -> saveAutostartNextSession(action.enabled) + is SettingsAction.SaveLockScreenInAod -> saveLockScreenInAod(action.enabled) is SettingsAction.SaveColorScheme -> saveColorScheme(action.color) is SettingsAction.SaveTheme -> saveTheme(action.theme) is SettingsAction.SaveBlackTheme -> saveBlackTheme(action.enabled) @@ -302,6 +303,18 @@ class SettingsViewModel( } } + private fun saveLockScreenInAod(lockScreenInAod: Boolean) { + viewModelScope.launch { + _settingsState.update { currentState -> + currentState.copy(lockScreenInAod = lockScreenInAod) + } + preferenceRepository.saveBooleanPreference( + "lock_screen_in_aod", + lockScreenInAod + ) + } + } + suspend fun reloadSettings() { var settingsState = _settingsState.value val focusTime = @@ -375,6 +388,8 @@ class SettingsViewModel( "autostart_next_session", settingsState.autostartNextSession ) + val lockScreenInAod = preferenceRepository.getBooleanPreference("lock_screen_in_aod") + ?: preferenceRepository.saveBooleanPreference("lock_screen_in_aod", true) _settingsState.update { currentState -> currentState.copy( @@ -392,7 +407,8 @@ class SettingsViewModel( dndEnabled = dndEnabled, mediaVolumeForAlarm = mediaVolumeForAlarm, singleProgressBar = singleProgressBar, - autostartNextSession = autostartNextSession + autostartNextSession = autostartNextSession, + lockScreenInAod = lockScreenInAod ) } diff --git a/app/src/main/res/drawable/mobile_lock_portrait.xml b/app/src/main/res/drawable/mobile_lock_portrait.xml new file mode 100644 index 0000000..4c8a264 --- /dev/null +++ b/app/src/main/res/drawable/mobile_lock_portrait.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0d2b3dd..0c54743 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,7 +15,7 @@ ~ If not, see . --> - + Alarm Ring alarm when a timer completes Alarm sound @@ -68,7 +68,7 @@ Focus durations at different times of the day Rate on Google Play Restart - Selected + Selected Session length Focus intervals in one session: %1$d Settings @@ -107,6 +107,8 @@ Plays on headphones only. If headphones are disconnected, alarm plays through speaker at media volume. Session-only progress Show progress for the current session only in notifications, rather than the full sequence. - Start next session after stopping an alarm - Auto start next session + Start next timer after stopping an alarm + Auto start next timer + Secure AOD + Automatically lock your device after a timeout, while keeping the AOD visible \ No newline at end of file From 8e414cdee62444025c77c1bd3ca6e5cffd044bf6 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Tue, 9 Dec 2025 10:22:18 +0530 Subject: [PATCH 13/20] feat(settings): implement secure lock screen settings behaviour Closes: #166 --- .../org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt | 8 ++++++-- .../main/java/org/nsh07/pomodoro/ui/AppScreen.kt | 6 +++++- .../ui/settingsScreen/screens/TimerSettings.kt | 6 +++--- .../settingsScreen/viewModel/SettingsAction.kt | 2 +- .../ui/settingsScreen/viewModel/SettingsState.kt | 2 +- .../viewModel/SettingsViewModel.kt | 16 ++++++++-------- 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt b/app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt index 10f3ea3..ae1e8f0 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt @@ -83,6 +83,7 @@ import kotlin.random.Random @Composable fun SharedTransitionScope.AlwaysOnDisplay( timerState: TimerState, + secureAod: Boolean, progress: () -> Float, setTimerFrequency: (Float) -> Unit, modifier: Modifier = Modifier @@ -100,8 +101,10 @@ fun SharedTransitionScope.AlwaysOnDisplay( DisposableEffect(Unit) { setTimerFrequency(1f) window.addFlags( - WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or - WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + if (secureAod) { + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or + WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + } else WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON ) activity?.setShowWhenLocked(true) insetsController.apply { @@ -273,6 +276,7 @@ private fun AlwaysOnDisplayPreview() { SharedTransitionLayout { AlwaysOnDisplay( timerState = timerState, + secureAod = true, progress = progress, setTimerFrequency = {} ) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt index 60c528b..6ccacf3 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt @@ -88,6 +88,7 @@ import androidx.window.core.layout.WindowSizeClass import org.nsh07.pomodoro.billing.TomatoPlusPaywallDialog import org.nsh07.pomodoro.service.TimerService import org.nsh07.pomodoro.ui.settingsScreen.SettingsScreenRoot +import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel import org.nsh07.pomodoro.ui.statsScreen.StatsScreenRoot import org.nsh07.pomodoro.ui.timerScreen.AlarmDialog import org.nsh07.pomodoro.ui.timerScreen.TimerScreen @@ -101,11 +102,13 @@ fun AppScreen( isPlus: Boolean, setTimerFrequency: (Float) -> Unit, modifier: Modifier = Modifier, - timerViewModel: TimerViewModel = viewModel(factory = TimerViewModel.Factory) + timerViewModel: TimerViewModel = viewModel(factory = TimerViewModel.Factory), + settingsViewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory) ) { val context = LocalContext.current val uiState by timerViewModel.timerState.collectAsStateWithLifecycle() + val settingsState by settingsViewModel.settingsState.collectAsStateWithLifecycle() val progress by timerViewModel.progress.collectAsStateWithLifecycle() val layoutDirection = LocalLayoutDirection.current @@ -278,6 +281,7 @@ fun AppScreen( entry { AlwaysOnDisplay( timerState = uiState, + secureAod = settingsState.secureAod, progress = { progress }, setTimerFrequency = setTimerFrequency, modifier = if (isAODEnabled) Modifier.clickable { 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 5aad189..b9ab50b 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 @@ -131,7 +131,7 @@ fun TimerSettings( settingsState.dndEnabled, settingsState.aodEnabled, settingsState.autostartNextSession, - settingsState.lockScreenInAod, + settingsState.secureAod, isPlus, serviceRunning ) { @@ -177,12 +177,12 @@ fun TimerSettings( onClick = { onAction(SettingsAction.SaveAodEnabled(it)) } ), SettingsSwitchItem( - checked = settingsState.lockScreenInAod && isPlus, + checked = settingsState.secureAod && isPlus, enabled = isPlus, icon = R.drawable.mobile_lock_portrait, label = R.string.secure_aod, description = R.string.secure_aod_desc, - onClick = { onAction(SettingsAction.SaveLockScreenInAod(it)) } + onClick = { onAction(SettingsAction.SaveSecureAod(it)) } ) ) ) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt index b9cf69b..f7ee061 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt @@ -29,7 +29,7 @@ sealed interface SettingsAction { data class SaveMediaVolumeForAlarm(val enabled: Boolean) : SettingsAction data class SaveSingleProgressBar(val enabled: Boolean) : SettingsAction data class SaveAutostartNextSession(val enabled: Boolean) : SettingsAction - data class SaveLockScreenInAod(val enabled: Boolean) : SettingsAction + data class SaveSecureAod(val enabled: Boolean) : SettingsAction data class SaveAlarmSound(val uri: Uri?) : SettingsAction data class SaveTheme(val theme: String) : SettingsAction data class SaveColorScheme(val color: Color) : SettingsAction diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt index 1ecdd23..c6fb3ea 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt @@ -34,7 +34,7 @@ data class SettingsState( val mediaVolumeForAlarm: Boolean = false, val singleProgressBar: Boolean = false, val autostartNextSession: Boolean = false, - val lockScreenInAod: Boolean = true, + val secureAod: Boolean = true, val focusTime: Long = 25 * 60 * 1000L, val shortBreakTime: Long = 5 * 60 * 1000L, 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 2ab6250..99cee0e 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 @@ -115,7 +115,7 @@ class SettingsViewModel( is SettingsAction.SaveMediaVolumeForAlarm -> saveMediaVolumeForAlarm(action.enabled) is SettingsAction.SaveSingleProgressBar -> saveSingleProgressBar(action.enabled) is SettingsAction.SaveAutostartNextSession -> saveAutostartNextSession(action.enabled) - is SettingsAction.SaveLockScreenInAod -> saveLockScreenInAod(action.enabled) + is SettingsAction.SaveSecureAod -> saveSecureAod(action.enabled) is SettingsAction.SaveColorScheme -> saveColorScheme(action.color) is SettingsAction.SaveTheme -> saveTheme(action.theme) is SettingsAction.SaveBlackTheme -> saveBlackTheme(action.enabled) @@ -303,14 +303,14 @@ class SettingsViewModel( } } - private fun saveLockScreenInAod(lockScreenInAod: Boolean) { + private fun saveSecureAod(secureAod: Boolean) { viewModelScope.launch { _settingsState.update { currentState -> - currentState.copy(lockScreenInAod = lockScreenInAod) + currentState.copy(secureAod = secureAod) } preferenceRepository.saveBooleanPreference( - "lock_screen_in_aod", - lockScreenInAod + "secure_aod", + secureAod ) } } @@ -388,8 +388,8 @@ class SettingsViewModel( "autostart_next_session", settingsState.autostartNextSession ) - val lockScreenInAod = preferenceRepository.getBooleanPreference("lock_screen_in_aod") - ?: preferenceRepository.saveBooleanPreference("lock_screen_in_aod", true) + val secureAod = preferenceRepository.getBooleanPreference("secure_aod") + ?: preferenceRepository.saveBooleanPreference("secure_aod", true) _settingsState.update { currentState -> currentState.copy( @@ -408,7 +408,7 @@ class SettingsViewModel( mediaVolumeForAlarm = mediaVolumeForAlarm, singleProgressBar = singleProgressBar, autostartNextSession = autostartNextSession, - lockScreenInAod = lockScreenInAod + secureAod = secureAod ) } From 24e0f083ee52b56fb9642e04f36d89d4bc72bcbe Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 9 Dec 2025 05:28:14 +0100 Subject: [PATCH 14/20] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (93 of 93 strings) Co-authored-by: Hosted Weblate Co-authored-by: ezn24 Translate-URL: https://hosted.weblate.org/projects/tomato/strings/ Translate-URL: https://hosted.weblate.org/projects/tomato/strings/zh_Hant/ Translation: Tomato/Strings --- app/src/main/res/values-zh-rTW/strings.xml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a290702..9965c7a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -75,6 +75,19 @@ 選擇語言 在 Google Play 上評分 已選擇 - 協助翻譯 + 協助翻譯 Tomato 重置計時器以變更設定 + %1$d小時 %2$d分鐘 + %1$d小時 + %1$d分鐘 + 關於 + 將 Tomato 翻譯成你的語言 + 喜歡這個 App 嗎?寫個評論吧! + 以小額捐款支持我 + 使用 Tomato+ 進一步自訂 + 授權條款 + 耳機模式 + 僅透過耳機播放。若耳機斷線,鬧鐘將以媒體音量從喇叭播放。 + 僅顯示工作階段進度 + 在通知中僅顯示目前工作階段的進度,而非完整流程。 From 716f2114c5288ecd636a8ab5b29d1337bdc92eaf Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Tue, 9 Dec 2025 12:16:23 +0530 Subject: [PATCH 15/20] feat(service): add snackbar to undo a timer reset Closes #109 --- .../nsh07/pomodoro/data/StateRepository.kt | 3 ++ .../nsh07/pomodoro/service/ServiceHelper.kt | 6 +++ .../nsh07/pomodoro/service/TimerService.kt | 26 +++++++++- .../pomodoro/service/TimerStateSnapshot.kt | 48 +++++++++++++++++++ .../pomodoro/ui/timerScreen/TimerScreen.kt | 34 ++++++++++--- .../ui/timerScreen/viewModel/TimerAction.kt | 15 +++++- app/src/main/res/values/strings.xml | 2 + 7 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/org/nsh07/pomodoro/service/TimerStateSnapshot.kt 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 From 9d91530a3e937f06cac6810909b24dc54e476c9a Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Tue, 9 Dec 2025 18:40:11 +0530 Subject: [PATCH 16/20] fix(service): disable autostart of next session if alarm was stopped due to timeout --- .../java/org/nsh07/pomodoro/service/TimerService.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) 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 88aa940..7f7ae42 100644 --- a/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt +++ b/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt @@ -430,7 +430,7 @@ class TimerService : Service() { autoAlarmStopScope = CoroutineScope(Dispatchers.IO).launch { delay(1 * 60 * 1000) - stopAlarm() + stopAlarm(fromAutoStop = true) } if (settingsState.vibrateEnabled) { @@ -444,7 +444,13 @@ class TimerService : Service() { } } - fun stopAlarm() { + /** + * Stops ringing the alarm and vibration, and performs related necessary actions + * + * @param fromAutoStop Whether the function was triggered automatically by the program instead of + * intentionally by the user + */ + fun stopAlarm(fromAutoStop: Boolean = false) { val settingsState = _settingsState.value autoAlarmStopScope?.cancel() @@ -474,7 +480,8 @@ class TimerService : Service() { }, paused = true, complete = false ) - if (settingsState.autostartNextSession) toggleTimer() + if (settingsState.autostartNextSession && !fromAutoStop) // auto start next session + toggleTimer() } private fun initializeMediaPlayer(): MediaPlayer? { From 9362440c2577b1f167e3ed080a70ad2e0c2a2bf0 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Tue, 9 Dec 2025 19:17:38 +0530 Subject: [PATCH 17/20] fix(strings): fix incorrect terminology used in strings --- app/src/main/res/values/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e037f45..5487d01 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -81,7 +81,7 @@ Stats Stop Stop alarm - Current timer session is complete. Tap anywhere to stop the alarm. + Current timer is complete. Tap anywhere to stop the alarm. Stop Alarm? System Theme @@ -105,8 +105,8 @@ License Headphone mode Plays on headphones only. If headphones are disconnected, alarm plays through speaker at media volume. - Session-only progress - Show progress for the current session only in notifications, rather than the full sequence. + Current timer-only progress + Show progress for the current timer only in notifications, rather than the entire session Start next timer after stopping an alarm Auto start next timer Secure AOD From dab14bf5624097a2b7cd2859eeb167370449df35 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Tue, 9 Dec 2025 19:21:01 +0530 Subject: [PATCH 18/20] fix(strings): some more string changes --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5487d01..3755e19 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -105,7 +105,7 @@ License Headphone mode Plays on headphones only. If headphones are disconnected, alarm plays through speaker at media volume. - Current timer-only progress + Simplified progress Show progress for the current timer only in notifications, rather than the entire session Start next timer after stopping an alarm Auto start next timer From 8ee33695da601eec700b5e99d1b13f80afdef54f Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 9 Dec 2025 15:06:44 +0100 Subject: [PATCH 19/20] Translated using Weblate (Hindi) Currently translated at 100.0% (97 of 97 strings) Translated using Weblate (Hindi) Currently translated at 91.7% (89 of 97 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (97 of 97 strings) Translated using Weblate (Hindi) Currently translated at 91.7% (89 of 97 strings) Co-authored-by: AhmedAwad Co-authored-by: Hosted Weblate Co-authored-by: Nishant Mishra <66912344+nsh07@users.noreply.github.com> Translate-URL: https://hosted.weblate.org/projects/tomato/strings/ar/ Translate-URL: https://hosted.weblate.org/projects/tomato/strings/hi/ Translation: Tomato/Strings --- app/src/main/res/values-ar/strings.xml | 6 ++++++ app/src/main/res/values-hi/strings.xml | 23 +++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 4038ef2..34fc3e1 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -88,4 +88,10 @@ يعمل فقط على سماعات الرأس. إذا كانت سماعات الرأس مفصولة، يصدر المنبه عبر سماعة الهاتف عند مستوى صوت الوسائط. التقدم في الجلسة فقط عرض التقدم للجلسة الحالية بدلا من سلسلة الجلسات كلها + ابدأ المؤقت التالي بعد إيقاف المنبه + التشغيل التلقائي في المؤقت القادم + AOD الآمن + قم بقفل جهازك تلقائيا بعد انتهاء الوقت، مع إبقاء AOD مرئياً + إعادة ضبط المؤقت + تراجع diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 877508a..6903f56 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -47,7 +47,7 @@ अगला शुरू करें आँकड़े अलार्म बंद करें - वर्तमान टाइमर सत्र पूरा हो गया है। अलार्म बंद करने के लिए कहीं भी टैप करें। + वर्तमान टाइमर पूरा हो गया है। अलार्म बंद करने के लिए कहीं भी टैप करें। अलार्म बंद करें? सिस्टम थीम @@ -72,6 +72,25 @@ भाषा चुनें Google Play पर रेटिंग दें चयनित - अनुवाद में सहायता करें + Tomato के अनुवाद में मदद करें सेटिंग्स बदलने के लिए टाइमर रीसेट करें + %1$dघं %2$dमि + %1$dघं + %1$dमि + ऐप के बारे में + Tomato का अपनी भाषा में अनुवाद करें + ऐप पसंद आया? रिव्यू लिखें! + एक छोटे से दान से मेरी मदद करें + Tomato+ के साथ और कस्टमाइज़ करें + लाइसेंस + हेडफ़ोन मोड + अलार्म सिर्फ़ हेडफ़ोन पर बजता है. अगर हेडफ़ोन कनेक्ट नहीं हैं, तो अलार्म मीडिया वॉल्यूम पर स्पीकर से बजता है। + सरलीकृत प्रगति + पूरे सत्र के बजाय, नोटिफ़िकेशन में केवल मौजूदा टाइमर की प्रगति दिखाएँ + अलार्म बंद करने के बाद अगला टाइमर शुरू करें + टाइमर स्वयं आरंभ + सुरक्षित AOD + तय समय के बाद डिवाइस को स्वयं लॉक कर दें, जबकि AOD दिखता रहे + टाइमर रीसेट किया गया + पूर्ववत् करें From 0aab9482f01c333ff3c5c9658cdcb03438165779 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Tue, 9 Dec 2025 19:47:43 +0530 Subject: [PATCH 20/20] chore(release): bump version, update changelog --- app/build.gradle.kts | 4 ++-- fastlane/metadata/android/en-US/changelogs/23.txt | 9 +++++++++ gradle/libs.versions.toml | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/23.txt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f6a654f..7bb6185 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -43,8 +43,8 @@ android { applicationId = "org.nsh07.pomodoro" minSdk = 27 targetSdk = 36 - versionCode = 22 - versionName = "1.7.0" + versionCode = 23 + versionName = "1.7.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/fastlane/metadata/android/en-US/changelogs/23.txt b/fastlane/metadata/android/en-US/changelogs/23.txt new file mode 100644 index 0000000..f37343c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/23.txt @@ -0,0 +1,9 @@ +New features: +- AOD mode now uses a lighter font +- New option to auto start next session after stopping an alarm +- New option to disable locking screen while in AOD mode +- Accidentally reset the timer? You can now undo and correct your mistake ;) + +Fixes: +- Improved stats screen performance and fixed lag while opening stats screen +- Fixed incorrect alignment of text in navigation toolbar \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4f393a3..1eb1d0b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ ksp = "2.3.3" lifecycleRuntimeKtx = "2.10.0" materialKolor = "4.0.5" navigation3 = "1.0.0" -revenuecat = "9.15.1" +revenuecat = "9.15.2" room = "2.8.4" vico = "2.3.6"