feat(alarmas): surface scheduling failures on the alarm card
Wires EstadoAlarmas.ultimaExcepcionPara into PantallaAlarmas: an alarm with an outstanding scheduling-failure exception now shows a calm warning line (distinguishing a pre-notice-only failure from the alarm itself not being registered) with a tap target into the reliability diagnostics screen. The warning is its own small tap target nested inside the existing card InkWell, so tap-to-edit, swipe-to-delete and the hero "Saltar" chip are untouched. Adds alarmCardSchedulingFailedMessage/alarmCardPreNoticeFailedMessage to all 13 ARB locales with real per-language translations (verified against arb_parity_test and arb_anti_copy_test).
This commit is contained in:
@@ -286,6 +286,20 @@ class _TarjetaAlarma extends StatelessWidget {
|
||||
if (pausadaPorVacaciones) l10n.alarmCardVacationPausedBadge,
|
||||
];
|
||||
|
||||
// fix/alarmas-fallos-silenciosos: `ultimaExcepcionPara` existed but was
|
||||
// never read from any screen, so a failed native scheduling attempt (main
|
||||
// alarm, pre-notice, foreground service, or a post-boot reschedule)
|
||||
// rendered exactly like a healthy alarm -- switched on, no visible sign
|
||||
// anything was wrong. `_esValida` only ever treats `tipoSaltoSiguiente`
|
||||
// as a real skip, so any OTHER tipo found here is a reliability failure,
|
||||
// never a deliberate user action.
|
||||
final ultimaExcepcion = estado.ultimaExcepcionPara(alarma.id);
|
||||
final fallo =
|
||||
ultimaExcepcion != null &&
|
||||
ExcepcionAlarma.tiposFallo.contains(ultimaExcepcion.tipo)
|
||||
? ultimaExcepcion
|
||||
: null;
|
||||
|
||||
return Dismissible(
|
||||
key: ValueKey('tarjeta-alarma-${alarma.id}'),
|
||||
direction: DismissDirection.horizontal,
|
||||
@@ -417,6 +431,14 @@ class _TarjetaAlarma extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
if (fallo != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
_AvisoFalloProgramacion(
|
||||
alarmaId: alarma.id,
|
||||
esSoloPreaviso:
|
||||
fallo.tipo == ExcepcionAlarma.tipoFalloPreaviso,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -470,6 +492,82 @@ class _TarjetaAlarma extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-alarm scheduling-failure notice (fix/alarmas-fallos-silenciosos):
|
||||
/// renders INSIDE the card's own content, in its own small tap target --
|
||||
/// the surrounding card `InkWell` (tap = edit) and `Dismissible` (swipe =
|
||||
/// delete) are untouched; this inner `InkWell` only claims its own region
|
||||
/// and pushes the diagnostics screen instead of opening the editor.
|
||||
class _AvisoFalloProgramacion extends StatelessWidget {
|
||||
const _AvisoFalloProgramacion({
|
||||
required this.alarmaId,
|
||||
required this.esSoloPreaviso,
|
||||
});
|
||||
|
||||
final String alarmaId;
|
||||
|
||||
/// True when only the pre-notice reminder failed (the alarm itself is
|
||||
/// still scheduled) -- the user's reported symptom explicitly called out
|
||||
/// a missing pre-notice as distinct from the alarm never ringing at all,
|
||||
/// so the message must not conflate the two.
|
||||
final bool esSoloPreaviso;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final color = Theme.of(context).colorScheme.error;
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
key: ValueKey('tarjeta-alarma-fallo-$alarmaId'),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => _abrirDiagnostico(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.warning_amber_rounded, size: 15, color: color),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
esSoloPreaviso
|
||||
? l10n.alarmCardPreNoticeFailedMessage
|
||||
: l10n.alarmCardSchedulingFailedMessage,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
l10n.androidReliabilityReview,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: color,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _abrirDiagnostico(BuildContext context) {
|
||||
PluriPushScaffold.push(context, (_) => const PantallaDiagnosticoAlarmas());
|
||||
}
|
||||
}
|
||||
|
||||
/// Swipe-to-delete reveal, shown on both sides so either swipe direction
|
||||
/// works regardless of locale text direction.
|
||||
class _FondoSwipeEliminarAlarma extends StatelessWidget {
|
||||
|
||||
Reference in New Issue
Block a user