fix(alarm): unfreeze snooze modal and add per-minute snooze countdown

The snooze button was fire-and-forget without error handling: if
posponerAlarma threw (e.g. native scheduleAlarm returns false on a
device without exact-alarm permission), _dismissScreen never ran. The
stuck modal also kept _alarmaSonandoActiva true, which made the next
ring get ignored. _posponer/_detener now dismiss in a finally and stop
audio defensively.

Add a native, AlarmManager-driven snooze countdown notification that
re-posts every minute ("Rings in N min", 3->2->1) while the engine is
dead. scheduleSpec drives scheduleSnoozeCountdown for snoozes (instead
of the 30-min pre-notice). Localized text and button labels travel
Dart->Kotlin as {minutes} templates, same pattern as preNoticeTemplate.

Notification actions: "snooze again" (snoozeAgain, anchored to now)
reports back via the existing snoozed event; "stop" (cancelSnooze)
records a handled occurrence for cold-start reconciliation and emits a
new snoozeCancelled event handled in EstadoAlarmas.

Adds snoozeCountdown/snoozeAgainAction keys across all 13 locales and a
test for the snoozeCancelled event. Kotlin changes are static-reviewed
only; no Android build environment available here.
This commit is contained in:
Javier Bautista Fernández
2026-06-30 15:27:05 +02:00
parent 89ff6a3912
commit 481944815f
34 changed files with 683 additions and 14 deletions
+12
View File
@@ -2312,6 +2312,18 @@ abstract class AppLocalizations {
/// **'Empieza en {minutes} min'**
String preNoticeCountdown(int minutes);
/// No description provided for @snoozeCountdown.
///
/// In es, this message translates to:
/// **'Suena en {minutes} min'**
String snoozeCountdown(int minutes);
/// No description provided for @snoozeAgainAction.
///
/// In es, this message translates to:
/// **'Posponer otra vez'**
String get snoozeAgainAction;
/// No description provided for @eqDeviceEditTitle.
///
/// In es, this message translates to:
+8
View File
@@ -1253,6 +1253,14 @@ class AppLocalizationsAr extends AppLocalizations {
return 'يبدأ خلال $minutes دقيقة';
}
@override
String snoozeCountdown(int minutes) {
return 'يرنّ خلال $minutes دقيقة';
}
@override
String get snoozeAgainAction => 'غفوة مرة أخرى';
@override
String get eqDeviceEditTitle => 'تعديل الجهاز';
+8
View File
@@ -1260,6 +1260,14 @@ class AppLocalizationsBn extends AppLocalizations {
return '$minutes মিনিটে শুরু হবে';
}
@override
String snoozeCountdown(int minutes) {
return '$minutes মিনিটে বাজবে';
}
@override
String get snoozeAgainAction => 'আবার স্নুজ করুন';
@override
String get eqDeviceEditTitle => 'ডিভাইস সম্পাদনা করুন';
+8
View File
@@ -1270,6 +1270,14 @@ class AppLocalizationsDe extends AppLocalizations {
return 'Startet in $minutes Min.';
}
@override
String snoozeCountdown(int minutes) {
return 'Klingelt in $minutes Min.';
}
@override
String get snoozeAgainAction => 'Erneut schlummern';
@override
String get eqDeviceEditTitle => 'Gerät bearbeiten';
+8
View File
@@ -1256,6 +1256,14 @@ class AppLocalizationsEn extends AppLocalizations {
return 'Starts in $minutes min';
}
@override
String snoozeCountdown(int minutes) {
return 'Rings in $minutes min';
}
@override
String get snoozeAgainAction => 'Snooze again';
@override
String get eqDeviceEditTitle => 'Edit device';
+8
View File
@@ -1265,6 +1265,14 @@ class AppLocalizationsEs extends AppLocalizations {
return 'Empieza en $minutes min';
}
@override
String snoozeCountdown(int minutes) {
return 'Suena en $minutes min';
}
@override
String get snoozeAgainAction => 'Posponer otra vez';
@override
String get eqDeviceEditTitle => 'Editar dispositivo';
+8
View File
@@ -1275,6 +1275,14 @@ class AppLocalizationsFr extends AppLocalizations {
return 'Démarre dans $minutes min';
}
@override
String snoozeCountdown(int minutes) {
return 'Sonne dans $minutes min';
}
@override
String get snoozeAgainAction => 'Reporter encore';
@override
String get eqDeviceEditTitle => 'Modifier l\'appareil';
+8
View File
@@ -1259,6 +1259,14 @@ class AppLocalizationsHi extends AppLocalizations {
return '$minutes मिनट में शुरू होगा';
}
@override
String snoozeCountdown(int minutes) {
return '$minutes मिनट में बजेगा';
}
@override
String get snoozeAgainAction => 'फिर से स्नूज़ करें';
@override
String get eqDeviceEditTitle => 'डिवाइस संपादित करें';
+8
View File
@@ -1264,6 +1264,14 @@ class AppLocalizationsId extends AppLocalizations {
return 'Mulai dalam $minutes menit';
}
@override
String snoozeCountdown(int minutes) {
return 'Berbunyi dalam $minutes mnt';
}
@override
String get snoozeAgainAction => 'Tunda lagi';
@override
String get eqDeviceEditTitle => 'Edit perangkat';
+8
View File
@@ -1270,6 +1270,14 @@ class AppLocalizationsIt extends AppLocalizations {
return 'Inizia tra $minutes min';
}
@override
String snoozeCountdown(int minutes) {
return 'Suona tra $minutes min';
}
@override
String get snoozeAgainAction => 'Posponi di nuovo';
@override
String get eqDeviceEditTitle => 'Modifica dispositivo';
+8
View File
@@ -1220,6 +1220,14 @@ class AppLocalizationsJa extends AppLocalizations {
return '$minutes分後に開始';
}
@override
String snoozeCountdown(int minutes) {
return '$minutes分後に鳴ります';
}
@override
String get snoozeAgainAction => 'もう一度スヌーズ';
@override
String get eqDeviceEditTitle => 'デバイスを編集';
+8
View File
@@ -1262,6 +1262,14 @@ class AppLocalizationsPt extends AppLocalizations {
return 'Começa em $minutes min';
}
@override
String snoozeCountdown(int minutes) {
return 'Toca em $minutes min';
}
@override
String get snoozeAgainAction => 'Adiar novamente';
@override
String get eqDeviceEditTitle => 'Editar dispositivo';
+8
View File
@@ -1266,6 +1266,14 @@ class AppLocalizationsRu extends AppLocalizations {
return 'Начнётся через $minutes мин';
}
@override
String snoozeCountdown(int minutes) {
return 'Прозвонит через $minutes мин';
}
@override
String get snoozeAgainAction => 'Отложить снова';
@override
String get eqDeviceEditTitle => 'Изменить устройство';
+8
View File
@@ -1213,6 +1213,14 @@ class AppLocalizationsZh extends AppLocalizations {
return '$minutes分钟后开始';
}
@override
String snoozeCountdown(int minutes) {
return '$minutes分钟后响铃';
}
@override
String get snoozeAgainAction => '再次小睡';
@override
String get eqDeviceEditTitle => '编辑设备';