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
@@ -20,10 +20,16 @@ void main() {
return true;
case 'requestIgnoreBatteryOptimizations':
return true;
case 'openNotificationSettings':
return true;
case 'getActiveRingingAlarmId':
return 'ring1';
case 'stopActiveAlarm':
return {'stopped': true, 'wasRinging': true, 'activeAlarmId': 'ring1'};
return {
'stopped': true,
'wasRinging': true,
'activeAlarmId': 'ring1',
};
}
return null;
});
@@ -111,6 +117,22 @@ void main() {
},
);
test(
'abrirConfiguracionNotificaciones invoca openNotificationSettings '
'(deep link a Settings, distinto del permiso runtime de la primera vez)',
() async {
final servicio = ServicioAlarmasAndroid(channel: channel);
final abierto = await servicio.abrirConfiguracionNotificaciones();
expect(abierto, isTrue);
expect(
llamadas.map((c) => c.method),
contains('openNotificationSettings'),
);
},
);
test(
'detenerSonidoActivo mapea el resultado nativo confirmado a ResultadoDetencion',
() async {
@@ -144,21 +166,15 @@ void main() {
},
);
test(
'alarmaSonandoId propaga el error del canal (fail-toward-silence, '
'Finding 2)',
() async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) async {
llamadas.add(call);
throw PlatformException(code: 'QUERY_FAILED', message: 'boom');
});
final servicio = ServicioAlarmasAndroid(channel: channel);
test('alarmaSonandoId propaga el error del canal (fail-toward-silence, '
'Finding 2)', () async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) async {
llamadas.add(call);
throw PlatformException(code: 'QUERY_FAILED', message: 'boom');
});
final servicio = ServicioAlarmasAndroid(channel: channel);
expect(
() => servicio.alarmaSonandoId(),
throwsA(isA<PlatformException>()),
);
},
);
expect(() => servicio.alarmaSonandoId(), throwsA(isA<PlatformException>()));
});
}