feat(alarmas): replace one-line reliability button with full diagnostics screen

Surface all six DiagnosticoAlarmasAndroid fields instead of three: the
battery-optimization exemption and native pending-alarm count were
already collected but silently dropped by the old widget. Each failing
signal now offers a "Fix this" action that opens the right system
settings screen (exact alarms, notifications, full-screen intent,
battery optimization), guarded by SDK level and never crashing when a
ROM lacks that screen. Manufacturers known for aggressive background
killing (Xiaomi/Redmi/POCO, Huawei, Oppo, Vivo, OnePlus, Samsung) get
an honest explanation that Autostart must be enabled manually, since
there is no API to detect or grant it.

Notifications now deep-links straight to
ACTION_APP_NOTIFICATION_SETTINGS via a new openNotificationSettings
native method, instead of reusing the runtime permission popup meant
for first-time alarm creation.

New copy is added to all 13 ARB locales with real per-language
translations (not Spanish copies), verified by the ARB parity and
anti-copy tests plus the corruption scanner.
This commit is contained in:
2026-07-31 19:10:52 +02:00
parent ef9705a30e
commit 049ab78acb
35 changed files with 2262 additions and 79 deletions
+42 -40
View File
@@ -18,6 +18,7 @@ import '../widgets/pluri_layout.dart';
import '../widgets/pluri_push_scaffold.dart';
import '../widgets/pluri_root_header.dart';
import '../widgets/pluri_sleep_timer_sheet.dart';
import 'pantalla_diagnostico_alarmas.dart';
import 'pantalla_vacaciones.dart';
class PantallaAlarmas extends StatelessWidget {
@@ -340,8 +341,7 @@ class _TarjetaAlarma extends StatelessWidget {
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme
.onSurface
color: Theme.of(context).colorScheme.onSurface
.withValues(alpha: 0.5),
),
),
@@ -404,7 +404,9 @@ class _TarjetaAlarma extends StatelessWidget {
const SizedBox(height: 3),
Text(
detalles.join(' · '),
key: ValueKey('tarjeta-alarma-detalles-${alarma.id}'),
key: ValueKey(
'tarjeta-alarma-detalles-${alarma.id}',
),
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 11,
@@ -1337,6 +1339,16 @@ class _SelectorEmisoraSheetState extends State<_SelectorEmisoraSheet> {
}
}
/// Entry point into the full Android alarm-reliability diagnostics screen
/// (fix/alarmas-fiabilidad). Was a one-line `TextButton.icon` that only ever
/// surfaced 3 of the 6 fields `DiagnosticoAlarmasAndroid` collects (exact
/// alarms, notifications, full-screen intent) and cycled all three
/// permission requests on a single tap; the two most diagnostic fields --
/// battery-optimization exemption and the native pending-alarm count, which
/// tells the user whether the alarm ever reached the OS at all -- were
/// gathered and never shown. Now a tap target row (mirrors
/// `_PanelVacaciones`'s shape) pushing `PantallaDiagnosticoAlarmas`, which
/// shows every signal individually with its own fix action.
class _AccesoDiagnostico extends StatelessWidget {
const _AccesoDiagnostico({required this.estado});
@@ -1345,47 +1357,37 @@ class _AccesoDiagnostico extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final diag = estado.diagnostico;
final exactStatus =
diag?.puedeProgramarExactas == true
? l10n.statusOk
: l10n.statusPending;
final notificationStatus =
diag?.notificacionesPermitidas == true
? l10n.statusOk
: l10n.statusPending;
final screenStatus =
diag?.puedeUsarPantallaCompleta == true
? l10n.statusOk
: l10n.statusPending;
return TextButton.icon(
icon: const _AssetIcon(
'assets/icons/alarmas/android_reliability.png',
size: 28,
),
label: Text(
diag == null
? l10n.androidReliabilityTitle
: l10n.androidReliabilityStatus(
exactStatus,
notificationStatus,
screenStatus,
final tokens = context.pluriTokens;
return PluriGlassSurface(
padding: EdgeInsets.zero,
child: Material(
type: MaterialType.transparency,
child: InkWell(
key: const ValueKey('diagnostico-alarmas-resumen'),
borderRadius: BorderRadius.circular(tokens.radiusMd),
onTap: () => _abrirDiagnostico(context),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
const _AssetIcon(
'assets/icons/alarmas/android_reliability.png',
size: 28,
),
const SizedBox(width: 10),
Expanded(child: Text(l10n.androidReliabilityReview)),
const Icon(Icons.chevron_right_rounded),
],
),
),
),
),
onPressed: () async {
if (diag != null && !diag.puedeProgramarExactas) {
await estado.android.solicitarPermisoAlarmasExactas();
}
if (diag != null && !diag.notificacionesPermitidas) {
await estado.android.solicitarPermisoNotificaciones();
}
if (diag != null && !diag.puedeUsarPantallaCompleta) {
await estado.android.solicitarPermisoPantallaCompleta();
}
await estado.cargarDiagnostico();
},
);
}
void _abrirDiagnostico(BuildContext context) {
PluriPushScaffold.push(context, (_) => const PantallaDiagnosticoAlarmas());
}
}
/// Vacation summary row (alarm-vacation-ranges delta, WU8): replaces the old