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:
@@ -248,4 +248,61 @@ void main() {
|
||||
expect(android.programadas.last.snoozeHasta, isNull);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'evento nativo snoozeCancelled limpia el snooze y avanza sin reprogramar',
|
||||
() async {
|
||||
final ahora = DateTime(2026, 6, 11, 7, 32);
|
||||
final android = FakePuertoAlarmasAndroid();
|
||||
final estado = EstadoAlarmas(
|
||||
servicio: ServicioAlarmas(reloj: () => ahora),
|
||||
android: android,
|
||||
iniciarAutomaticamente: false,
|
||||
);
|
||||
addTearDown(estado.dispose);
|
||||
addTearDown(android.dispose);
|
||||
await estado.guardarAlarma(
|
||||
AlarmaMusical(
|
||||
id: 'cancel1',
|
||||
nombre: 'Diaria',
|
||||
hora: 7,
|
||||
minuto: 30,
|
||||
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
||||
diasSemana: const [],
|
||||
proximaEjecucion: DateTime(2026, 6, 11, 7, 30),
|
||||
snoozeHasta: DateTime(2026, 6, 11, 7, 35),
|
||||
snoozeOrigen: DateTime(2026, 6, 11, 7, 30),
|
||||
),
|
||||
);
|
||||
expect(estado.alarmas.single.snoozeHasta, isNotNull);
|
||||
final programadasAntes = android.programadas.length;
|
||||
final notificado = Completer<void>();
|
||||
estado.addListener(() {
|
||||
if (!notificado.isCompleted) notificado.complete();
|
||||
});
|
||||
|
||||
android.emitirEvento(
|
||||
EventoAlarmaAndroid(
|
||||
alarmaId: 'cancel1',
|
||||
titulo: 'Diaria',
|
||||
accion: EventoAlarmaAndroid.accionSnoozeCancelled,
|
||||
occurrenceAtMillis:
|
||||
DateTime(2026, 6, 11, 7, 30).millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
await notificado.future;
|
||||
|
||||
expect(estado.alarmas.single.snoozeHasta, isNull);
|
||||
expect(
|
||||
estado.alarmas.single.proximaEjecucion,
|
||||
DateTime(2026, 6, 12, 7, 30),
|
||||
reason: 'avanza a la próxima ocurrencia diaria',
|
||||
);
|
||||
expect(
|
||||
android.programadas.length,
|
||||
programadasAntes,
|
||||
reason: 'el nativo ya reprogramó; no debe haber un segundo programar',
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user