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.
294 lines
10 KiB
Dart
294 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import '../estado/estado_alarmas.dart';
|
|
import '../l10n/gen/app_localizations.dart';
|
|
import '../servicios/diagnostico_alarmas.dart';
|
|
import '../servicios/servicio_alarmas_android.dart';
|
|
import '../tema/pluriwave_theme.dart';
|
|
import '../widgets/pluri_glass_surface.dart';
|
|
import '../widgets/pluri_layout.dart';
|
|
import '../widgets/pluri_push_scaffold.dart';
|
|
|
|
/// Full Android alarm-reliability diagnostics screen (fix/alarmas-fiabilidad).
|
|
///
|
|
/// Replaces the old one-line `_AccesoDiagnostico` button in
|
|
/// `pantalla_alarmas.dart`, which only ever surfaced 3 of the 6 fields
|
|
/// `DiagnosticoAlarmasAndroid` collects. This screen shows all five
|
|
/// diagnosable signals with a clear ok/needs-attention state, a "Fix this"
|
|
/// action that opens the right system settings screen for each failing one,
|
|
/// plus manufacturer-specific guidance for vendors known to require manually
|
|
/// enabling Autostart.
|
|
class PantallaDiagnosticoAlarmas extends StatelessWidget {
|
|
const PantallaDiagnosticoAlarmas({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final l10n = AppLocalizations.of(context);
|
|
final estado = context.watch<EstadoAlarmas>();
|
|
final diag = estado.diagnostico;
|
|
|
|
return PluriPushScaffold(
|
|
title: l10n.androidReliabilityTitle,
|
|
body:
|
|
diag == null
|
|
? ListView(
|
|
padding: PluriLayout.pageContentPadding,
|
|
children: [
|
|
PluriGlassSurface(
|
|
child: Text(l10n.alarmDiagnosticsUnavailableHint),
|
|
),
|
|
],
|
|
)
|
|
: _CuerpoDiagnostico(estado: estado, diag: diag),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CuerpoDiagnostico extends StatelessWidget {
|
|
const _CuerpoDiagnostico({required this.estado, required this.diag});
|
|
|
|
final EstadoAlarmas estado;
|
|
final DiagnosticoAlarmasAndroid diag;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final l10n = AppLocalizations.of(context);
|
|
final items = construirItemsDiagnosticoAlarmas(
|
|
diagnostico: diag,
|
|
hayAlarmasActivas: estado.alarmas.any((alarma) => alarma.activa),
|
|
);
|
|
final mostrarAutostart = fabricanteRequiereGuiaAutostart(diag.fabricante);
|
|
|
|
return ListView(
|
|
padding: PluriLayout.pageContentPadding,
|
|
children: [
|
|
for (final item in items) ...[
|
|
_FilaDiagnostico(item: item, estado: estado, diag: diag),
|
|
const SizedBox(height: 10),
|
|
],
|
|
const SizedBox(height: 6),
|
|
PluriGlassSurface(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_FilaInformativa(
|
|
titulo: l10n.alarmDiagnosticsManufacturerLabel,
|
|
valor: diag.fabricante,
|
|
),
|
|
const SizedBox(height: 10),
|
|
_FilaInformativa(
|
|
titulo: l10n.alarmDiagnosticsSdkLabel,
|
|
valor: diag.versionSdk.toString(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (mostrarAutostart) ...[
|
|
const SizedBox(height: 16),
|
|
_GuiaAutostart(fabricante: diag.fabricante),
|
|
],
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _FilaDiagnostico extends StatelessWidget {
|
|
const _FilaDiagnostico({
|
|
required this.item,
|
|
required this.estado,
|
|
required this.diag,
|
|
});
|
|
|
|
final ItemDiagnosticoAlarma item;
|
|
final EstadoAlarmas estado;
|
|
final DiagnosticoAlarmasAndroid diag;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final l10n = AppLocalizations.of(context);
|
|
final tokens = context.pluriTokens;
|
|
final ok = item.estado == EstadoSenalDiagnostico.ok;
|
|
final color = ok ? tokens.liveGreen : Theme.of(context).colorScheme.error;
|
|
final esConteoNativo =
|
|
item.senal == SenalDiagnosticoAlarma.alarmasNativasPendientes;
|
|
|
|
return PluriGlassSurface(
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Icon(
|
|
ok ? Icons.check_circle_rounded : Icons.warning_amber_rounded,
|
|
color: color,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
_tituloSenal(l10n, item.senal),
|
|
style: const TextStyle(fontWeight: FontWeight.w700),
|
|
),
|
|
),
|
|
Text(
|
|
ok
|
|
? l10n.statusOk
|
|
: l10n.alarmDiagnosticsNeedsAttentionStatus,
|
|
style: TextStyle(
|
|
color: color,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 4),
|
|
if (esConteoNativo) ...[
|
|
Text(
|
|
l10n.alarmDiagnosticsNativeCountValue(
|
|
diag.alarmasNativasPendientes,
|
|
),
|
|
),
|
|
if (!ok) ...[
|
|
const SizedBox(height: 4),
|
|
Text(l10n.alarmDiagnosticsNativeCountAttentionHint),
|
|
],
|
|
] else
|
|
Text(_hintSenal(l10n, item.senal)),
|
|
if (!ok && item.accion != AccionDiagnosticoAlarma.ninguna) ...[
|
|
const SizedBox(height: 8),
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
child: OutlinedButton(
|
|
onPressed: () => _ejecutarAccion(context, l10n),
|
|
child: Text(l10n.alarmDiagnosticsFixAction),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Runs the system action for [item.accion] and reloads the diagnostic
|
|
/// snapshot. Never throws across the widget boundary: every underlying
|
|
/// `PuertoAlarmasAndroid` call already reports `false` instead (native side
|
|
/// catches any intent-resolution failure), and a `false` here surfaces a
|
|
/// calm SnackBar instead of leaving the tap looking like a no-op.
|
|
Future<void> _ejecutarAccion(
|
|
BuildContext context,
|
|
AppLocalizations l10n,
|
|
) async {
|
|
final resuelto = switch (item.accion) {
|
|
AccionDiagnosticoAlarma.abrirAlarmasExactas =>
|
|
await estado.android.solicitarPermisoAlarmasExactas(),
|
|
AccionDiagnosticoAlarma.abrirNotificaciones =>
|
|
await estado.android.abrirConfiguracionNotificaciones(),
|
|
AccionDiagnosticoAlarma.abrirOptimizacionBateria =>
|
|
await estado.android.solicitarExencionBateria(),
|
|
AccionDiagnosticoAlarma.abrirPantallaCompleta =>
|
|
await estado.android.solicitarPermisoPantallaCompleta(),
|
|
AccionDiagnosticoAlarma.ninguna => true,
|
|
};
|
|
await estado.cargarDiagnostico();
|
|
if (!resuelto && context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(l10n.alarmDiagnosticsIntentUnavailable)),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
String _tituloSenal(AppLocalizations l10n, SenalDiagnosticoAlarma senal) =>
|
|
switch (senal) {
|
|
SenalDiagnosticoAlarma.alarmasExactas =>
|
|
l10n.alarmDiagnosticsExactAlarmsTitle,
|
|
SenalDiagnosticoAlarma.notificaciones =>
|
|
l10n.alarmDiagnosticsNotificationsTitle,
|
|
SenalDiagnosticoAlarma.pantallaCompleta =>
|
|
l10n.alarmDiagnosticsFullScreenTitle,
|
|
SenalDiagnosticoAlarma.optimizacionBateria =>
|
|
l10n.alarmDiagnosticsBatteryTitle,
|
|
SenalDiagnosticoAlarma.alarmasNativasPendientes =>
|
|
l10n.alarmDiagnosticsNativeCountTitle,
|
|
};
|
|
|
|
/// Static one-line explanation per signal. `alarmasNativasPendientes` builds
|
|
/// its own dynamic body in [_FilaDiagnostico] instead (count + conditional
|
|
/// attention hint), so this branch is never actually rendered for it -- kept
|
|
/// only so the switch stays exhaustive over the enum.
|
|
String _hintSenal(
|
|
AppLocalizations l10n,
|
|
SenalDiagnosticoAlarma senal,
|
|
) => switch (senal) {
|
|
SenalDiagnosticoAlarma.alarmasExactas => l10n.alarmDiagnosticsExactAlarmsHint,
|
|
SenalDiagnosticoAlarma.notificaciones =>
|
|
l10n.alarmDiagnosticsNotificationsHint,
|
|
SenalDiagnosticoAlarma.pantallaCompleta =>
|
|
l10n.alarmDiagnosticsFullScreenHint,
|
|
SenalDiagnosticoAlarma.optimizacionBateria =>
|
|
l10n.alarmDiagnosticsBatteryHint,
|
|
SenalDiagnosticoAlarma.alarmasNativasPendientes => '',
|
|
};
|
|
|
|
class _FilaInformativa extends StatelessWidget {
|
|
const _FilaInformativa({required this.titulo, required this.valor});
|
|
|
|
final String titulo;
|
|
final String valor;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(titulo),
|
|
Text(valor, style: const TextStyle(fontWeight: FontWeight.w700)),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Manufacturer-specific autostart explanation (fix/alarmas-fiabilidad item
|
|
/// 3). Deliberately never claims the app can detect or grant this setting --
|
|
/// there is no public API for it, so this is explanation only, never an
|
|
/// action button.
|
|
class _GuiaAutostart extends StatelessWidget {
|
|
const _GuiaAutostart({required this.fabricante});
|
|
|
|
final String fabricante;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final l10n = AppLocalizations.of(context);
|
|
final tokens = context.pluriTokens;
|
|
return PluriGlassSurface(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.info_outline_rounded, color: tokens.warmCoral),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(
|
|
l10n.alarmDiagnosticsAutostartTitle,
|
|
style: const TextStyle(fontWeight: FontWeight.w700),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(l10n.alarmDiagnosticsAutostartBody(fabricante)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|