From c107c0e18a07f51d7fcb4330335ef0c4150297a6 Mon Sep 17 00:00:00 2001 From: freetlab Date: Fri, 31 Jul 2026 21:19:57 +0200 Subject: [PATCH] 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). --- lib/l10n/app_ar.arb | 2 + lib/l10n/app_bn.arb | 2 + lib/l10n/app_de.arb | 2 + lib/l10n/app_en.arb | 2 + lib/l10n/app_es.arb | 2 + lib/l10n/app_fr.arb | 2 + lib/l10n/app_hi.arb | 2 + lib/l10n/app_id.arb | 2 + lib/l10n/app_it.arb | 2 + lib/l10n/app_ja.arb | 2 + lib/l10n/app_pt.arb | 2 + lib/l10n/app_ru.arb | 2 + lib/l10n/app_zh.arb | 2 + lib/l10n/gen/app_localizations.dart | 12 ++ lib/l10n/gen/app_localizations_ar.dart | 8 + lib/l10n/gen/app_localizations_bn.dart | 8 + lib/l10n/gen/app_localizations_de.dart | 8 + lib/l10n/gen/app_localizations_en.dart | 8 + lib/l10n/gen/app_localizations_es.dart | 8 + lib/l10n/gen/app_localizations_fr.dart | 8 + lib/l10n/gen/app_localizations_hi.dart | 8 + lib/l10n/gen/app_localizations_id.dart | 8 + lib/l10n/gen/app_localizations_it.dart | 8 + lib/l10n/gen/app_localizations_ja.dart | 8 + lib/l10n/gen/app_localizations_pt.dart | 8 + lib/l10n/gen/app_localizations_ru.dart | 8 + lib/l10n/gen/app_localizations_zh.dart | 6 + lib/pantallas/pantalla_alarmas.dart | 98 +++++++++++ ...talla_alarmas_fallo_programacion_test.dart | 154 ++++++++++++++++++ 29 files changed, 392 insertions(+) create mode 100644 test/pantallas/pantalla_alarmas_fallo_programacion_test.dart diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index b649158..ba591d7 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -842,6 +842,8 @@ } }, "alarmCardVacationPausedBadge": "متوقفة مؤقتًا بسبب الإجازة", + "alarmCardSchedulingFailedMessage": "لم يتم تسجيل هذا المنبه في النظام، لذا قد لا يرن.", + "alarmCardPreNoticeFailedMessage": "تمت جدولة هذا المنبه، لكن تعذّر ضبط تذكيره المسبق.", "alarmDiagnosticsExactAlarmsTitle": "جدولة المنبه بدقة", "alarmDiagnosticsExactAlarmsHint": "يتيح رنين المنبه في الدقيقة المحددة تمامًا، حتى عندما يكون الهاتف في وضع السكون.", "alarmDiagnosticsNotificationsTitle": "الإشعارات", diff --git a/lib/l10n/app_bn.arb b/lib/l10n/app_bn.arb index 11615c1..66eba91 100644 --- a/lib/l10n/app_bn.arb +++ b/lib/l10n/app_bn.arb @@ -842,6 +842,8 @@ } }, "alarmCardVacationPausedBadge": "ছুটির কারণে বিরত", + "alarmCardSchedulingFailedMessage": "এই অ্যালার্মটি সিস্টেমে নিবন্ধন করা যায়নি, তাই এটি নাও বাজতে পারে।", + "alarmCardPreNoticeFailedMessage": "এই অ্যালার্মটি নির্ধারিত হয়েছে, তবে এর আগাম রিমাইন্ডার সেট করা যায়নি।", "alarmDiagnosticsExactAlarmsTitle": "নির্ভুল অ্যালার্ম শিডিউলিং", "alarmDiagnosticsExactAlarmsHint": "ফোন ঘুমন্ত অবস্থায় থাকলেও অ্যালার্মকে ঠিক নির্ধারিত মিনিটে বাজতে দেয়।", "alarmDiagnosticsNotificationsTitle": "বিজ্ঞপ্তি", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index d972c03..4e3b18b 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -842,6 +842,8 @@ } }, "alarmCardVacationPausedBadge": "Wegen Urlaub pausiert", + "alarmCardSchedulingFailedMessage": "Dieser Alarm konnte nicht im System registriert werden, daher klingelt er möglicherweise nicht.", + "alarmCardPreNoticeFailedMessage": "Dieser Alarm ist geplant, aber seine Vorwarnung konnte nicht eingerichtet werden.", "alarmDiagnosticsExactAlarmsTitle": "Genaue Alarmplanung", "alarmDiagnosticsExactAlarmsHint": "Lässt den Alarm genau zur eingestellten Minute klingeln, auch wenn das Telefon im Ruhezustand ist.", "alarmDiagnosticsNotificationsTitle": "Benachrichtigungen", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 097fcca..7a3e740 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -842,6 +842,8 @@ } }, "alarmCardVacationPausedBadge": "Paused for vacation", + "alarmCardSchedulingFailedMessage": "This alarm could not be registered with the system, so it may not ring.", + "alarmCardPreNoticeFailedMessage": "This alarm is scheduled, but its early reminder could not be set.", "alarmDiagnosticsExactAlarmsTitle": "Exact alarm scheduling", "alarmDiagnosticsExactAlarmsHint": "Lets the alarm ring at the exact minute you set, even while the phone is asleep.", "alarmDiagnosticsNotificationsTitle": "Notifications", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 2e0f8c7..696c698 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -801,6 +801,8 @@ } }, "alarmCardVacationPausedBadge": "Pausada por vacaciones", + "alarmCardSchedulingFailedMessage": "Esta alarma no se pudo registrar en el sistema, así que podría no sonar.", + "alarmCardPreNoticeFailedMessage": "Esta alarma está programada, pero no se pudo activar su aviso previo.", "alarmDiagnosticsExactAlarmsTitle": "Programación de alarma exacta", "alarmDiagnosticsExactAlarmsHint": "Permite que la alarma suene en el minuto exacto que elegiste, incluso con el teléfono en reposo.", "alarmDiagnosticsNotificationsTitle": "Notificaciones", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 9267ffb..e1abd72 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -842,6 +842,8 @@ } }, "alarmCardVacationPausedBadge": "En pause pour les vacances", + "alarmCardSchedulingFailedMessage": "Cette alarme n'a pas pu être enregistrée dans le système ; elle risque donc de ne pas sonner.", + "alarmCardPreNoticeFailedMessage": "Cette alarme est programmée, mais son rappel anticipé n'a pas pu être activé.", "alarmDiagnosticsExactAlarmsTitle": "Programmation d'alarme précise", "alarmDiagnosticsExactAlarmsHint": "Permet à l'alarme de sonner à la minute exacte choisie, même si le téléphone est en veille.", "alarmDiagnosticsNotificationsTitle": "Notifications", diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index d08ec56..3addd01 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -842,6 +842,8 @@ } }, "alarmCardVacationPausedBadge": "छुट्टी के कारण रोका गया", + "alarmCardSchedulingFailedMessage": "यह अलार्म सिस्टम में दर्ज नहीं हो सका, इसलिए यह शायद न बजे।", + "alarmCardPreNoticeFailedMessage": "यह अलार्म शेड्यूल किया गया है, लेकिन इसकी पूर्व-चेतावनी सेट नहीं हो सकी।", "alarmDiagnosticsExactAlarmsTitle": "सटीक अलार्म शेड्यूलिंग", "alarmDiagnosticsExactAlarmsHint": "फ़ोन के सुप्त मोड में होने पर भी अलार्म को ठीक तय किए गए मिनट पर बजने देता है।", "alarmDiagnosticsNotificationsTitle": "सूचनाएं", diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index dcb8c90..7af1ab3 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -842,6 +842,8 @@ } }, "alarmCardVacationPausedBadge": "Dijeda karena liburan", + "alarmCardSchedulingFailedMessage": "Alarm ini tidak dapat didaftarkan ke sistem, sehingga mungkin tidak berbunyi.", + "alarmCardPreNoticeFailedMessage": "Alarm ini sudah dijadwalkan, tetapi pengingat awalnya tidak dapat diatur.", "alarmDiagnosticsExactAlarmsTitle": "Penjadwalan alarm presisi", "alarmDiagnosticsExactAlarmsHint": "Membuat alarm berbunyi tepat pada menit yang kamu atur, meski ponsel dalam mode tidur.", "alarmDiagnosticsNotificationsTitle": "Notifikasi", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index aeb9ee9..5677f83 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -842,6 +842,8 @@ } }, "alarmCardVacationPausedBadge": "In pausa per le vacanze", + "alarmCardSchedulingFailedMessage": "Questa sveglia non è stata registrata nel sistema, quindi potrebbe non suonare.", + "alarmCardPreNoticeFailedMessage": "Questa sveglia è programmata, ma non è stato possibile impostare il promemoria anticipato.", "alarmDiagnosticsExactAlarmsTitle": "Programmazione sveglia esatta", "alarmDiagnosticsExactAlarmsHint": "Permette alla sveglia di suonare esattamente al minuto impostato, anche a telefono in stand-by.", "alarmDiagnosticsNotificationsTitle": "Notifiche", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index d0151af..444554b 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -842,6 +842,8 @@ } }, "alarmCardVacationPausedBadge": "休暇のため一時停止中", + "alarmCardSchedulingFailedMessage": "このアラームはシステムに登録できなかったため、鳴らない可能性があります。", + "alarmCardPreNoticeFailedMessage": "このアラームは設定されていますが、事前通知を設定できませんでした。", "alarmDiagnosticsExactAlarmsTitle": "正確なアラームのスケジュール設定", "alarmDiagnosticsExactAlarmsHint": "スマートフォンがスリープ中でも、設定した時刻ちょうどにアラームを鳴らせるようにします。", "alarmDiagnosticsNotificationsTitle": "通知", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index b234893..2b41a67 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -842,6 +842,8 @@ } }, "alarmCardVacationPausedBadge": "Pausada por férias", + "alarmCardSchedulingFailedMessage": "Este alarme não pôde ser registrado no sistema, por isso pode não tocar.", + "alarmCardPreNoticeFailedMessage": "Este alarme está agendado, mas seu aviso antecipado não pôde ser configurado.", "alarmDiagnosticsExactAlarmsTitle": "Agendamento exato do alarme", "alarmDiagnosticsExactAlarmsHint": "Permite que o alarme toque no minuto exato definido, mesmo com o telefone em repouso.", "alarmDiagnosticsNotificationsTitle": "Notificações", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 32e3f7b..c370a09 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -842,6 +842,8 @@ } }, "alarmCardVacationPausedBadge": "Приостановлено на время отпуска", + "alarmCardSchedulingFailedMessage": "Этот будильник не удалось зарегистрировать в системе, поэтому он может не сработать.", + "alarmCardPreNoticeFailedMessage": "Этот будильник запланирован, но не удалось настроить предварительное напоминание.", "alarmDiagnosticsExactAlarmsTitle": "Точное планирование будильника", "alarmDiagnosticsExactAlarmsHint": "Будильник звонит ровно в заданную минуту, даже если телефон находится в режиме сна.", "alarmDiagnosticsNotificationsTitle": "Уведомления", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 0a5d154..f27a9cc 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -842,6 +842,8 @@ } }, "alarmCardVacationPausedBadge": "因假期已暂停", + "alarmCardSchedulingFailedMessage": "该闹钟未能在系统中注册,因此可能不会响铃。", + "alarmCardPreNoticeFailedMessage": "该闹钟已设置,但其提前提醒未能设置成功。", "alarmDiagnosticsExactAlarmsTitle": "精确闹钟排程", "alarmDiagnosticsExactAlarmsHint": "即使手机处于休眠状态,也能让闹钟在设定的准确时间响起。", "alarmDiagnosticsNotificationsTitle": "通知", diff --git a/lib/l10n/gen/app_localizations.dart b/lib/l10n/gen/app_localizations.dart index 22bef5f..81cac10 100644 --- a/lib/l10n/gen/app_localizations.dart +++ b/lib/l10n/gen/app_localizations.dart @@ -3074,6 +3074,18 @@ abstract class AppLocalizations { /// **'Pausada por vacaciones'** String get alarmCardVacationPausedBadge; + /// No description provided for @alarmCardSchedulingFailedMessage. + /// + /// In es, this message translates to: + /// **'Esta alarma no se pudo registrar en el sistema, así que podría no sonar.'** + String get alarmCardSchedulingFailedMessage; + + /// No description provided for @alarmCardPreNoticeFailedMessage. + /// + /// In es, this message translates to: + /// **'Esta alarma está programada, pero no se pudo activar su aviso previo.'** + String get alarmCardPreNoticeFailedMessage; + /// No description provided for @alarmDiagnosticsExactAlarmsTitle. /// /// In es, this message translates to: diff --git a/lib/l10n/gen/app_localizations_ar.dart b/lib/l10n/gen/app_localizations_ar.dart index 40605e7..5726266 100644 --- a/lib/l10n/gen/app_localizations_ar.dart +++ b/lib/l10n/gen/app_localizations_ar.dart @@ -1691,6 +1691,14 @@ class AppLocalizationsAr extends AppLocalizations { @override String get alarmCardVacationPausedBadge => 'متوقفة مؤقتًا بسبب الإجازة'; + @override + String get alarmCardSchedulingFailedMessage => + 'لم يتم تسجيل هذا المنبه في النظام، لذا قد لا يرن.'; + + @override + String get alarmCardPreNoticeFailedMessage => + 'تمت جدولة هذا المنبه، لكن تعذّر ضبط تذكيره المسبق.'; + @override String get alarmDiagnosticsExactAlarmsTitle => 'جدولة المنبه بدقة'; diff --git a/lib/l10n/gen/app_localizations_bn.dart b/lib/l10n/gen/app_localizations_bn.dart index ae3f5aa..c7939b9 100644 --- a/lib/l10n/gen/app_localizations_bn.dart +++ b/lib/l10n/gen/app_localizations_bn.dart @@ -1700,6 +1700,14 @@ class AppLocalizationsBn extends AppLocalizations { @override String get alarmCardVacationPausedBadge => 'ছুটির কারণে বিরত'; + @override + String get alarmCardSchedulingFailedMessage => + 'এই অ্যালার্মটি সিস্টেমে নিবন্ধন করা যায়নি, তাই এটি নাও বাজতে পারে।'; + + @override + String get alarmCardPreNoticeFailedMessage => + 'এই অ্যালার্মটি নির্ধারিত হয়েছে, তবে এর আগাম রিমাইন্ডার সেট করা যায়নি।'; + @override String get alarmDiagnosticsExactAlarmsTitle => 'নির্ভুল অ্যালার্ম শিডিউলিং'; diff --git a/lib/l10n/gen/app_localizations_de.dart b/lib/l10n/gen/app_localizations_de.dart index 3be5612..a6c33af 100644 --- a/lib/l10n/gen/app_localizations_de.dart +++ b/lib/l10n/gen/app_localizations_de.dart @@ -1713,6 +1713,14 @@ class AppLocalizationsDe extends AppLocalizations { @override String get alarmCardVacationPausedBadge => 'Wegen Urlaub pausiert'; + @override + String get alarmCardSchedulingFailedMessage => + 'Dieser Alarm konnte nicht im System registriert werden, daher klingelt er möglicherweise nicht.'; + + @override + String get alarmCardPreNoticeFailedMessage => + 'Dieser Alarm ist geplant, aber seine Vorwarnung konnte nicht eingerichtet werden.'; + @override String get alarmDiagnosticsExactAlarmsTitle => 'Genaue Alarmplanung'; diff --git a/lib/l10n/gen/app_localizations_en.dart b/lib/l10n/gen/app_localizations_en.dart index 19433cc..d73df11 100644 --- a/lib/l10n/gen/app_localizations_en.dart +++ b/lib/l10n/gen/app_localizations_en.dart @@ -1693,6 +1693,14 @@ class AppLocalizationsEn extends AppLocalizations { @override String get alarmCardVacationPausedBadge => 'Paused for vacation'; + @override + String get alarmCardSchedulingFailedMessage => + 'This alarm could not be registered with the system, so it may not ring.'; + + @override + String get alarmCardPreNoticeFailedMessage => + 'This alarm is scheduled, but its early reminder could not be set.'; + @override String get alarmDiagnosticsExactAlarmsTitle => 'Exact alarm scheduling'; diff --git a/lib/l10n/gen/app_localizations_es.dart b/lib/l10n/gen/app_localizations_es.dart index 1a201ea..cc6fc0d 100644 --- a/lib/l10n/gen/app_localizations_es.dart +++ b/lib/l10n/gen/app_localizations_es.dart @@ -1707,6 +1707,14 @@ class AppLocalizationsEs extends AppLocalizations { @override String get alarmCardVacationPausedBadge => 'Pausada por vacaciones'; + @override + String get alarmCardSchedulingFailedMessage => + 'Esta alarma no se pudo registrar en el sistema, así que podría no sonar.'; + + @override + String get alarmCardPreNoticeFailedMessage => + 'Esta alarma está programada, pero no se pudo activar su aviso previo.'; + @override String get alarmDiagnosticsExactAlarmsTitle => 'Programación de alarma exacta'; diff --git a/lib/l10n/gen/app_localizations_fr.dart b/lib/l10n/gen/app_localizations_fr.dart index 2c935cf..a0e0a48 100644 --- a/lib/l10n/gen/app_localizations_fr.dart +++ b/lib/l10n/gen/app_localizations_fr.dart @@ -1716,6 +1716,14 @@ class AppLocalizationsFr extends AppLocalizations { @override String get alarmCardVacationPausedBadge => 'En pause pour les vacances'; + @override + String get alarmCardSchedulingFailedMessage => + 'Cette alarme n\'a pas pu être enregistrée dans le système ; elle risque donc de ne pas sonner.'; + + @override + String get alarmCardPreNoticeFailedMessage => + 'Cette alarme est programmée, mais son rappel anticipé n\'a pas pu être activé.'; + @override String get alarmDiagnosticsExactAlarmsTitle => 'Programmation d\'alarme précise'; diff --git a/lib/l10n/gen/app_localizations_hi.dart b/lib/l10n/gen/app_localizations_hi.dart index b8fd3de..c6bf8fb 100644 --- a/lib/l10n/gen/app_localizations_hi.dart +++ b/lib/l10n/gen/app_localizations_hi.dart @@ -1695,6 +1695,14 @@ class AppLocalizationsHi extends AppLocalizations { @override String get alarmCardVacationPausedBadge => 'छुट्टी के कारण रोका गया'; + @override + String get alarmCardSchedulingFailedMessage => + 'यह अलार्म सिस्टम में दर्ज नहीं हो सका, इसलिए यह शायद न बजे।'; + + @override + String get alarmCardPreNoticeFailedMessage => + 'यह अलार्म शेड्यूल किया गया है, लेकिन इसकी पूर्व-चेतावनी सेट नहीं हो सकी।'; + @override String get alarmDiagnosticsExactAlarmsTitle => 'सटीक अलार्म शेड्यूलिंग'; diff --git a/lib/l10n/gen/app_localizations_id.dart b/lib/l10n/gen/app_localizations_id.dart index d8b2eca..3a3c234 100644 --- a/lib/l10n/gen/app_localizations_id.dart +++ b/lib/l10n/gen/app_localizations_id.dart @@ -1703,6 +1703,14 @@ class AppLocalizationsId extends AppLocalizations { @override String get alarmCardVacationPausedBadge => 'Dijeda karena liburan'; + @override + String get alarmCardSchedulingFailedMessage => + 'Alarm ini tidak dapat didaftarkan ke sistem, sehingga mungkin tidak berbunyi.'; + + @override + String get alarmCardPreNoticeFailedMessage => + 'Alarm ini sudah dijadwalkan, tetapi pengingat awalnya tidak dapat diatur.'; + @override String get alarmDiagnosticsExactAlarmsTitle => 'Penjadwalan alarm presisi'; diff --git a/lib/l10n/gen/app_localizations_it.dart b/lib/l10n/gen/app_localizations_it.dart index 4c8e8f8..ad1a334 100644 --- a/lib/l10n/gen/app_localizations_it.dart +++ b/lib/l10n/gen/app_localizations_it.dart @@ -1715,6 +1715,14 @@ class AppLocalizationsIt extends AppLocalizations { @override String get alarmCardVacationPausedBadge => 'In pausa per le vacanze'; + @override + String get alarmCardSchedulingFailedMessage => + 'Questa sveglia non è stata registrata nel sistema, quindi potrebbe non suonare.'; + + @override + String get alarmCardPreNoticeFailedMessage => + 'Questa sveglia è programmata, ma non è stato possibile impostare il promemoria anticipato.'; + @override String get alarmDiagnosticsExactAlarmsTitle => 'Programmazione sveglia esatta'; diff --git a/lib/l10n/gen/app_localizations_ja.dart b/lib/l10n/gen/app_localizations_ja.dart index 76b4d03..c6f045b 100644 --- a/lib/l10n/gen/app_localizations_ja.dart +++ b/lib/l10n/gen/app_localizations_ja.dart @@ -1646,6 +1646,14 @@ class AppLocalizationsJa extends AppLocalizations { @override String get alarmCardVacationPausedBadge => '休暇のため一時停止中'; + @override + String get alarmCardSchedulingFailedMessage => + 'このアラームはシステムに登録できなかったため、鳴らない可能性があります。'; + + @override + String get alarmCardPreNoticeFailedMessage => + 'このアラームは設定されていますが、事前通知を設定できませんでした。'; + @override String get alarmDiagnosticsExactAlarmsTitle => '正確なアラームのスケジュール設定'; diff --git a/lib/l10n/gen/app_localizations_pt.dart b/lib/l10n/gen/app_localizations_pt.dart index 9067f87..88f3343 100644 --- a/lib/l10n/gen/app_localizations_pt.dart +++ b/lib/l10n/gen/app_localizations_pt.dart @@ -1703,6 +1703,14 @@ class AppLocalizationsPt extends AppLocalizations { @override String get alarmCardVacationPausedBadge => 'Pausada por férias'; + @override + String get alarmCardSchedulingFailedMessage => + 'Este alarme não pôde ser registrado no sistema, por isso pode não tocar.'; + + @override + String get alarmCardPreNoticeFailedMessage => + 'Este alarme está agendado, mas seu aviso antecipado não pôde ser configurado.'; + @override String get alarmDiagnosticsExactAlarmsTitle => 'Agendamento exato do alarme'; diff --git a/lib/l10n/gen/app_localizations_ru.dart b/lib/l10n/gen/app_localizations_ru.dart index 9559e89..aceb85b 100644 --- a/lib/l10n/gen/app_localizations_ru.dart +++ b/lib/l10n/gen/app_localizations_ru.dart @@ -1707,6 +1707,14 @@ class AppLocalizationsRu extends AppLocalizations { @override String get alarmCardVacationPausedBadge => 'Приостановлено на время отпуска'; + @override + String get alarmCardSchedulingFailedMessage => + 'Этот будильник не удалось зарегистрировать в системе, поэтому он может не сработать.'; + + @override + String get alarmCardPreNoticeFailedMessage => + 'Этот будильник запланирован, но не удалось настроить предварительное напоминание.'; + @override String get alarmDiagnosticsExactAlarmsTitle => 'Точное планирование будильника'; diff --git a/lib/l10n/gen/app_localizations_zh.dart b/lib/l10n/gen/app_localizations_zh.dart index 71638d3..c5a2dde 100644 --- a/lib/l10n/gen/app_localizations_zh.dart +++ b/lib/l10n/gen/app_localizations_zh.dart @@ -1638,6 +1638,12 @@ class AppLocalizationsZh extends AppLocalizations { @override String get alarmCardVacationPausedBadge => '因假期已暂停'; + @override + String get alarmCardSchedulingFailedMessage => '该闹钟未能在系统中注册,因此可能不会响铃。'; + + @override + String get alarmCardPreNoticeFailedMessage => '该闹钟已设置,但其提前提醒未能设置成功。'; + @override String get alarmDiagnosticsExactAlarmsTitle => '精确闹钟排程'; diff --git a/lib/pantallas/pantalla_alarmas.dart b/lib/pantallas/pantalla_alarmas.dart index 79edcc1..15da5bd 100644 --- a/lib/pantallas/pantalla_alarmas.dart +++ b/lib/pantallas/pantalla_alarmas.dart @@ -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 { diff --git a/test/pantallas/pantalla_alarmas_fallo_programacion_test.dart b/test/pantallas/pantalla_alarmas_fallo_programacion_test.dart new file mode 100644 index 0000000..f3d110c --- /dev/null +++ b/test/pantallas/pantalla_alarmas_fallo_programacion_test.dart @@ -0,0 +1,154 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pluriwave/estado/estado_alarmas.dart'; +import 'package:pluriwave/estado/estado_radio.dart'; +import 'package:pluriwave/l10n/gen/app_localizations.dart'; +import 'package:pluriwave/modelos/alarma_musical.dart'; +import 'package:pluriwave/pantallas/pantalla_alarmas.dart'; +import 'package:pluriwave/pantallas/pantalla_diagnostico_alarmas.dart'; +import 'package:pluriwave/servicios/servicio_alarmas.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../helpers/fakes.dart'; +import '../helpers/fakes_alarmas.dart'; + +/// Card-level visibility for a failed scheduling attempt (fix/alarmas- +/// fallos-silenciosos): before this, `ultimaExcepcionPara` existed but was +/// never read from any screen, so a failed alarm rendered exactly like a +/// working one -- switched on, no visible sign anything was wrong. +void main() { + setUp(() { + SharedPreferences.setMockInitialValues({}); + }); + + Future montarPantalla( + WidgetTester tester, + EstadoAlarmas estadoAlarmas, + ) async { + tester.view.physicalSize = const Size(1440, 3200); + tester.view.devicePixelRatio = 1.0; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + + final radio = EstadoRadio( + audio: FakeServicioAudio(), + favoritos: FakeServicioFavoritos(), + radio: FakeServicioRadio(), + servicioEcualizador: FakeServicioEcualizador(), + servicioGrabacion: FakeServicioGrabacionRadioInactiva(), + iniciarAutomaticamente: false, + ); + addTearDown(radio.dispose); + + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: radio), + ChangeNotifierProvider.value(value: estadoAlarmas), + ], + child: MaterialApp( + locale: const Locale('es'), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: const Scaffold(body: PantallaAlarmas()), + ), + ), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + } + + testWidgets( + 'una alarma cuya programacion fallo muestra un aviso en su tarjeta', + (tester) async { + final android = FakePuertoAlarmasAndroid()..fallaProgramar = true; + final estadoAlarmas = EstadoAlarmas( + servicio: ServicioAlarmas(reloj: () => DateTime(2026, 6, 1, 6, 0)), + android: android, + iniciarAutomaticamente: false, + ); + addTearDown(estadoAlarmas.dispose); + addTearDown(android.dispose); + await estadoAlarmas.guardarAlarma( + const AlarmaMusical( + id: 'r1', + nombre: 'Rota', + hora: 7, + minuto: 0, + tipoProgramacion: TipoProgramacionAlarma.diaria, + diasSemana: [], + ), + ); + + await montarPantalla(tester, estadoAlarmas); + + expect( + find.byKey(const ValueKey('tarjeta-alarma-fallo-r1')), + findsOneWidget, + ); + }, + ); + + testWidgets( + 'una alarma programada correctamente NO muestra el aviso de fallo', + (tester) async { + final android = FakePuertoAlarmasAndroid(); + final estadoAlarmas = EstadoAlarmas( + servicio: ServicioAlarmas(reloj: () => DateTime(2026, 6, 1, 6, 0)), + android: android, + iniciarAutomaticamente: false, + ); + addTearDown(estadoAlarmas.dispose); + addTearDown(android.dispose); + await estadoAlarmas.guardarAlarma( + const AlarmaMusical( + id: 'ok1', + nombre: 'Sana', + hora: 7, + minuto: 0, + tipoProgramacion: TipoProgramacionAlarma.diaria, + diasSemana: [], + ), + ); + + await montarPantalla(tester, estadoAlarmas); + + expect( + find.byKey(const ValueKey('tarjeta-alarma-fallo-ok1')), + findsNothing, + ); + }, + ); + + testWidgets('el aviso de fallo abre la pantalla de diagnostico al tocarlo', ( + tester, + ) async { + final android = FakePuertoAlarmasAndroid()..fallaProgramar = true; + final estadoAlarmas = EstadoAlarmas( + servicio: ServicioAlarmas(reloj: () => DateTime(2026, 6, 1, 6, 0)), + android: android, + iniciarAutomaticamente: false, + ); + addTearDown(estadoAlarmas.dispose); + addTearDown(android.dispose); + await estadoAlarmas.guardarAlarma( + const AlarmaMusical( + id: 'r1', + nombre: 'Rota', + hora: 7, + minuto: 0, + tipoProgramacion: TipoProgramacionAlarma.diaria, + diasSemana: [], + ), + ); + + await montarPantalla(tester, estadoAlarmas); + + await tester.tap(find.byKey(const ValueKey('tarjeta-alarma-fallo-r1'))); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 300)); + + expect(find.byType(PantallaDiagnosticoAlarmas), findsOneWidget); + }); +}