feat(app): a Pomodoro timer on Home, in the notification and in the top bar

Home has a Pomodoro timer (decided 2026-10-07, after Goodtime): the
profile it runs, chosen there while no timer is set, the time left of
the phase, and Start, Pause or Resume, Skip and Stop. While studying, a
small countdown sits in the study screen's header, and a tap pauses it
or goes on. A phase that ends leads to the next, which starts by itself
where the profile says so and otherwise waits, paused at its full
length.

Profiles follow the person to every device, in their settings (schema
4, still written as schema 3 while the profiles are as they come, so
older apps go on reading the settings): Classic, 25 minutes of focus,
5 of break and 15 after four, and 52/17 without long breaks, built in;
both can be changed, more added and deleted, from Home's card or from
Settings. A profile can start breaks and focus by themselves, and keep
the device quiet while focusing. The running timer is the device's own,
kept in the person's folder: closed and opened again, the app catches
up with the phases that ran out meanwhile.

Android: the app's own foreground service (Kotlin, specialUse, as
Goodtime does) shows an ongoing notification counting the phase down,
with Pause or Resume, Skip and Stop; on Android 16 it is a Live Update,
its progress bar the profile's cycle, each phase a segment, focus in
cobalt and breaks in copper. An exact alarm (USE_EXACT_ALARM) alerts
with sound and vibration when the phase ends, also with the app
closed, and Do Not Disturb goes on while focusing once the person
allowed it, the filter before given back afterwards. No notification
plugin: flutter_local_notifications has Live Updates only in a
prerelease that needs compileSdk 37; androidx.core 1.18, which the
build resolved already, has every API needed.

Linux, over the session bus (package:dbus, already in the build): where
the desktop shows icons in its top bar (GNOME with the AppIndicator
extension, which Ubuntu has built in; KDE) the timer counts down there,
the buttons in the icon's menu, as a StatusNotifierItem written in Dart
on the names and paths the snap's desktop plug allows; elsewhere a
notification with the buttons says until when the phase runs. An alert
with sound ends each phase while the app runs, and GNOME's banners stay
off while focusing, through gsettings. xdg-desktop-portal's Background
status, the planned fallback, serves Flatpaks only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-07 02:54:52 +02:00
commit 918e10bb2b
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -4737,6 +4737,257 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'{count, plural, =1{1 media file couldn\'t be imported.} other{{count} media files couldn\'t be imported.}}'**
String ankiImportFilesSkipped(int count);
/// No description provided for @pomodoroTitle.
///
/// In en, this message translates to:
/// **'Pomodoro'**
String get pomodoroTitle;
/// No description provided for @pomodoroFocus.
///
/// In en, this message translates to:
/// **'Focus'**
String get pomodoroFocus;
/// No description provided for @pomodoroShortBreak.
///
/// In en, this message translates to:
/// **'Short break'**
String get pomodoroShortBreak;
/// No description provided for @pomodoroLongBreak.
///
/// In en, this message translates to:
/// **'Long break'**
String get pomodoroLongBreak;
/// No description provided for @pomodoroFocusOf.
///
/// In en, this message translates to:
/// **'Focus {done} of {total}'**
String pomodoroFocusOf(int done, int total);
/// No description provided for @pomodoroStart.
///
/// In en, this message translates to:
/// **'Start'**
String get pomodoroStart;
/// No description provided for @pomodoroPause.
///
/// In en, this message translates to:
/// **'Pause'**
String get pomodoroPause;
/// No description provided for @pomodoroResume.
///
/// In en, this message translates to:
/// **'Resume'**
String get pomodoroResume;
/// No description provided for @pomodoroSkip.
///
/// In en, this message translates to:
/// **'Skip'**
String get pomodoroSkip;
/// No description provided for @pomodoroStop.
///
/// In en, this message translates to:
/// **'Stop'**
String get pomodoroStop;
/// No description provided for @pomodoroPaused.
///
/// In en, this message translates to:
/// **'Paused'**
String get pomodoroPaused;
/// No description provided for @pomodoroPausedProfile.
///
/// In en, this message translates to:
/// **'Paused · {profile}'**
String pomodoroPausedProfile(String profile);
/// No description provided for @pomodoroClassic.
///
/// In en, this message translates to:
/// **'Classic'**
String get pomodoroClassic;
/// No description provided for @pomodoroUnnamed.
///
/// In en, this message translates to:
/// **'Unnamed profile'**
String get pomodoroUnnamed;
/// No description provided for @pomodoroProfiles.
///
/// In en, this message translates to:
/// **'Pomodoro profiles'**
String get pomodoroProfiles;
/// No description provided for @pomodoroChooseProfile.
///
/// In en, this message translates to:
/// **'Choose a profile'**
String get pomodoroChooseProfile;
/// No description provided for @pomodoroAddProfile.
///
/// In en, this message translates to:
/// **'Add a profile'**
String get pomodoroAddProfile;
/// No description provided for @pomodoroEditProfile.
///
/// In en, this message translates to:
/// **'Edit profile'**
String get pomodoroEditProfile;
/// No description provided for @pomodoroName.
///
/// In en, this message translates to:
/// **'Name'**
String get pomodoroName;
/// No description provided for @pomodoroFocusLength.
///
/// In en, this message translates to:
/// **'Focus'**
String get pomodoroFocusLength;
/// No description provided for @pomodoroShortBreakLength.
///
/// In en, this message translates to:
/// **'Short break'**
String get pomodoroShortBreakLength;
/// No description provided for @pomodoroLongBreakLength.
///
/// In en, this message translates to:
/// **'Long break'**
String get pomodoroLongBreakLength;
/// No description provided for @pomodoroMinutes.
///
/// In en, this message translates to:
/// **'{count} min'**
String pomodoroMinutes(int count);
/// No description provided for @pomodoroNoLongBreak.
///
/// In en, this message translates to:
/// **'None'**
String get pomodoroNoLongBreak;
/// No description provided for @pomodoroLongBreakAfter.
///
/// In en, this message translates to:
/// **'Focus phases before a long break'**
String get pomodoroLongBreakAfter;
/// No description provided for @pomodoroAutoStartBreaks.
///
/// In en, this message translates to:
/// **'Start breaks by themselves'**
String get pomodoroAutoStartBreaks;
/// No description provided for @pomodoroAutoStartFocus.
///
/// In en, this message translates to:
/// **'Start focus by itself after a break'**
String get pomodoroAutoStartFocus;
/// No description provided for @pomodoroDoNotDisturb.
///
/// In en, this message translates to:
/// **'Do Not Disturb while focusing'**
String get pomodoroDoNotDisturb;
/// No description provided for @pomodoroDoNotDisturbHint.
///
/// In en, this message translates to:
/// **'The device keeps quiet while you focus; the alert at its end still sounds.'**
String get pomodoroDoNotDisturbHint;
/// No description provided for @pomodoroDoNotDisturbDenied.
///
/// In en, this message translates to:
/// **'The app may not keep this device quiet. Allow it in the system\'s settings.'**
String get pomodoroDoNotDisturbDenied;
/// No description provided for @pomodoroDeleteProfile.
///
/// In en, this message translates to:
/// **'Delete profile'**
String get pomodoroDeleteProfile;
/// No description provided for @pomodoroSummary.
///
/// In en, this message translates to:
/// **'{focus} min focus, {shortBreak} min break'**
String pomodoroSummary(int focus, int shortBreak);
/// No description provided for @pomodoroSummaryLong.
///
/// In en, this message translates to:
/// **'{focus} min focus, {shortBreak} min break, {longBreak} min after {after}'**
String pomodoroSummaryLong(
int focus,
int shortBreak,
int longBreak,
int after,
);
/// No description provided for @pomodoroAlertFocusDone.
///
/// In en, this message translates to:
/// **'Time for a break'**
String get pomodoroAlertFocusDone;
/// No description provided for @pomodoroAlertBreakDone.
///
/// In en, this message translates to:
/// **'Back to focus'**
String get pomodoroAlertBreakDone;
/// No description provided for @pomodoroAlertStarts.
///
/// In en, this message translates to:
/// **'{phase} has started.'**
String pomodoroAlertStarts(String phase);
/// No description provided for @pomodoroAlertWaits.
///
/// In en, this message translates to:
/// **'{phase} is ready when you are.'**
String pomodoroAlertWaits(String phase);
/// No description provided for @homeWidgetPomodoro.
///
/// In en, this message translates to:
/// **'Pomodoro timer'**
String get homeWidgetPomodoro;
/// No description provided for @pomodoroAlerts.
///
/// In en, this message translates to:
/// **'Pomodoro alerts'**
String get pomodoroAlerts;
/// No description provided for @pomodoroUntil.
///
/// In en, this message translates to:
/// **'Until {time}'**
String pomodoroUntil(String time);
/// No description provided for @pomodoroLeft.
///
/// In en, this message translates to:
/// **'{left} left'**
String pomodoroLeft(String left);
}
class _AppLocalizationsDelegate