fix(alarms): harden native playback and pre-notice actions

This commit is contained in:
Javier Bautista Fernández
2026-05-28 12:03:58 +02:00
parent 41bbd0ea17
commit 659e6da189
16 changed files with 1370 additions and 180 deletions
+21 -1
View File
@@ -241,6 +241,27 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
);
return;
}
if (evento.accion.endsWith('.POSTPONE_NEXT')) {
final ejecucion =
evento.occurrenceAtMillis > 0
? DateTime.fromMillisecondsSinceEpoch(evento.occurrenceAtMillis)
: alarma.proximaEjecucion ?? DateTime.now();
await estado.posponerProximaDesdePreaviso(
alarma,
evento.snoozeMinutes,
ejecucion,
);
if (!mounted) return;
setState(() => _indice = 3);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Alarma pospuesta ${evento.snoozeMinutes} min para esta ejecución.',
),
),
);
return;
}
if (evento.accion.endsWith('.PRE_NOTICE')) {
setState(() => _indice = 3);
return;
@@ -268,7 +289,6 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
_alarmaSonandoId = alarma.id;
try {
await alarmas.android.detenerSonidoNativo(alarma.id);
await _prearrancarAudioAlarma(alarma);
if (!mounted) return;
await Navigator.of(context).push(
+66 -10
View File
@@ -9,7 +9,7 @@ import '../servicios/servicio_alarmas_android.dart';
class EstadoAlarmas extends ChangeNotifier {
EstadoAlarmas({
ServicioAlarmas? servicio,
ServicioAlarmasAndroid? android,
PuertoAlarmasAndroid? android,
bool iniciarAutomaticamente = true,
}) : servicio = servicio ?? ServicioAlarmas(),
android = android ?? ServicioAlarmasAndroid() {
@@ -19,7 +19,7 @@ class EstadoAlarmas extends ChangeNotifier {
}
final ServicioAlarmas servicio;
final ServicioAlarmasAndroid android;
final PuertoAlarmasAndroid android;
List<AlarmaMusical> _alarmas = [];
List<RangoVacaciones> _vacaciones = [];
@@ -45,8 +45,10 @@ class EstadoAlarmas extends ChangeNotifier {
AlarmaMusical? get proximaAlarma {
final candidatas =
_alarmas.where((a) => a.activa && a.proximaEjecucion != null).toList()
..sort((a, b) => a.proximaEjecucion!.compareTo(b.proximaEjecucion!));
_alarmas.where((a) => a.activa && a.proximaProgramable != null).toList()
..sort(
(a, b) => a.proximaProgramable!.compareTo(b.proximaProgramable!),
);
return candidatas.isEmpty ? null : candidatas.first;
}
@@ -109,7 +111,7 @@ class EstadoAlarmas extends ChangeNotifier {
}
void marcarEjecucionGestionada(AlarmaMusical alarma) {
final proxima = alarma.proximaEjecucion;
final proxima = alarma.proximaProgramable;
if (proxima == null) return;
final key = '${alarma.id}:${proxima.millisecondsSinceEpoch}';
_ejecucionesEmitidas.add(key);
@@ -159,18 +161,62 @@ class EstadoAlarmas extends ChangeNotifier {
}
Future<void> posponerAlarma(AlarmaMusical alarma, int minutos) async {
final proxima = DateTime.now().add(Duration(minutes: minutos));
final ejecucion =
alarma.snoozeOrigen ?? alarma.proximaEjecucion ?? DateTime.now();
debugPrint(
'[PluriWave][alarmas] posponer id=${alarma.id} minutos=$minutos proxima=${proxima.toIso8601String()}',
'[PluriWave][alarmas] posponer id=${alarma.id} minutos=$minutos ejecucion=${ejecucion.toIso8601String()}',
);
await android.ocultarNotificacionAlarma(alarma.id);
await android.programar(alarma.copyWith(proximaEjecucion: proxima));
final config = await servicio.posponerEjecucion(
alarma.id,
ejecucion,
minutos,
);
_aplicar(config);
final actualizada = _buscarAlarma(alarma.id);
if (actualizada != null) {
await android.programar(actualizada);
}
notifyListeners();
}
Future<void> posponerProximaDesdePreaviso(
AlarmaMusical alarma,
int minutos,
DateTime ejecucion,
) async {
final seguros = _snoozeSeguro(minutos);
final snoozeHasta = ejecucion.add(Duration(minutes: seguros));
debugPrint(
'[PluriWave][alarmas] posponer desde preaviso id=${alarma.id} minutos=$seguros ejecucion=${ejecucion.toIso8601String()} hasta=${snoozeHasta.toIso8601String()}',
);
await android.ocultarNotificacionAlarma(alarma.id);
final config = await servicio.posponerEjecucionHasta(
alarma.id,
ejecucion,
snoozeHasta,
);
_aplicar(config);
final actualizada = _buscarAlarma(alarma.id);
if (actualizada != null) {
await android.programar(actualizada);
}
notifyListeners();
}
Future<void> finalizarEjecucion(String alarmaId) async {
debugPrint('[PluriWave][alarmas] finalizar ejecucion id=$alarmaId');
final alarma = _buscarAlarma(alarmaId);
final ejecucion =
alarma?.snoozeOrigen ??
alarma?.proximaEjecucion ??
alarma?.snoozeHasta ??
DateTime.now();
await android.ocultarNotificacionAlarma(alarmaId);
await refrescarProgramacion();
final config = await servicio.completarEjecucion(alarmaId, ejecucion);
_aplicar(config);
await _sincronizarTodas();
notifyListeners();
}
Future<void> crearRangoVacaciones(RangoVacaciones rango) async {
@@ -209,6 +255,16 @@ class EstadoAlarmas extends ChangeNotifier {
}
}
AlarmaMusical? _buscarAlarma(String id) {
for (final alarma in _alarmas) {
if (alarma.id == id) return alarma;
}
return null;
}
int _snoozeSeguro(int minutos) =>
minutos == 3 || minutos == 5 || minutos == 10 ? minutos : 5;
void _aplicar(ConfiguracionAlarmas config) {
_alarmas = config.alarmas;
_vacaciones = config.vacaciones;
@@ -230,7 +286,7 @@ class EstadoAlarmas extends ChangeNotifier {
void _vigilarAlarmasVencidas() {
final ahora = DateTime.now();
for (final alarma in _alarmas) {
final proxima = alarma.proximaEjecucion;
final proxima = alarma.proximaProgramable;
if (!alarma.activa || proxima == null) continue;
if (proxima.isAfter(ahora)) continue;
final key = '${alarma.id}:${proxima.millisecondsSinceEpoch}';
+28
View File
@@ -21,6 +21,9 @@ class AlarmaMusical {
this.volumen = 0.85,
this.sonidoInterno = SonidoInternoAlarma.amanecer,
this.proximaEjecucion,
this.snoozeHasta,
this.snoozeOrigen,
this.ultimaEjecucionGestionada,
this.creadaEn,
this.actualizadaEn,
});
@@ -40,6 +43,9 @@ class AlarmaMusical {
final double volumen;
final SonidoInternoAlarma sonidoInterno;
final DateTime? proximaEjecucion;
final DateTime? snoozeHasta;
final DateTime? snoozeOrigen;
final DateTime? ultimaEjecucionGestionada;
final DateTime? creadaEn;
final DateTime? actualizadaEn;
@@ -61,6 +67,11 @@ class AlarmaMusical {
SonidoInternoAlarma? sonidoInterno,
DateTime? proximaEjecucion,
bool limpiarProximaEjecucion = false,
DateTime? snoozeHasta,
DateTime? snoozeOrigen,
bool limpiarSnooze = false,
DateTime? ultimaEjecucionGestionada,
bool limpiarUltimaEjecucionGestionada = false,
DateTime? creadaEn,
DateTime? actualizadaEn,
}) {
@@ -83,11 +94,20 @@ class AlarmaMusical {
limpiarProximaEjecucion
? proximaEjecucion
: proximaEjecucion ?? this.proximaEjecucion,
snoozeHasta: limpiarSnooze ? snoozeHasta : snoozeHasta ?? this.snoozeHasta,
snoozeOrigen:
limpiarSnooze ? snoozeOrigen : snoozeOrigen ?? this.snoozeOrigen,
ultimaEjecucionGestionada:
limpiarUltimaEjecucionGestionada
? ultimaEjecucionGestionada
: ultimaEjecucionGestionada ?? this.ultimaEjecucionGestionada,
creadaEn: creadaEn ?? this.creadaEn,
actualizadaEn: actualizadaEn ?? this.actualizadaEn,
);
}
DateTime? get proximaProgramable => snoozeHasta ?? proximaEjecucion;
Map<String, dynamic> toJson() => {
'id': id,
'nombre': nombre,
@@ -104,6 +124,9 @@ class AlarmaMusical {
'volumen': volumen,
'sonidoInterno': sonidoInterno.name,
'proximaEjecucion': proximaEjecucion?.toIso8601String(),
'snoozeHasta': snoozeHasta?.toIso8601String(),
'snoozeOrigen': snoozeOrigen?.toIso8601String(),
'ultimaEjecucionGestionada': ultimaEjecucionGestionada?.toIso8601String(),
'creadaEn': creadaEn?.toIso8601String(),
'actualizadaEn': actualizadaEn?.toIso8601String(),
};
@@ -137,6 +160,11 @@ class AlarmaMusical {
SonidoInternoAlarma.amanecer,
),
proximaEjecucion: _dateFromJson(json['proximaEjecucion']),
snoozeHasta: _dateFromJson(json['snoozeHasta']),
snoozeOrigen: _dateFromJson(json['snoozeOrigen']),
ultimaEjecucionGestionada: _dateFromJson(
json['ultimaEjecucionGestionada'],
),
creadaEn: _dateFromJson(json['creadaEn']),
actualizadaEn: _dateFromJson(json['actualizadaEn']),
);
@@ -30,6 +30,7 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
Timer? _fallbackTimer;
bool _fallbackActivo = false;
bool _radioIntentada = false;
bool _audioFlutterConfirmado = false;
@override
void initState() {
@@ -57,6 +58,7 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
_estadoSub = radio.estadoStream.listen((estado) {
if (estado == EstadoReproduccion.reproduciendo && mounted) {
_fallbackTimer?.cancel();
_confirmarAudioFlutterListo();
}
if (estado == EstadoReproduccion.error && mounted) {
_iniciarFallback();
@@ -76,9 +78,18 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
_fallbackActivo = true;
await _fallbackPlayer.setAsset(_assetFallback(widget.alarma.sonidoInterno));
await _fallbackPlayer.play();
await _confirmarAudioFlutterListo();
if (mounted) setState(() {});
}
Future<void> _confirmarAudioFlutterListo() async {
if (_audioFlutterConfirmado) return;
_audioFlutterConfirmado = true;
await context.read<EstadoAlarmas>().android.confirmarAudioFlutter(
widget.alarma.id,
);
}
Future<void> _detener() async {
final radio = context.read<EstadoRadio>();
final alarmas = context.read<EstadoAlarmas>();
+19 -12
View File
@@ -82,8 +82,9 @@ class _PanelProximaAlarma extends StatelessWidget {
final proxima = estado.proximaAlarma;
final activasSinProxima =
estado.alarmas
.where((a) => a.activa && a.proximaEjecucion == null)
.where((a) => a.activa && a.proximaProgramable == null)
.length;
final proximaProgramable = proxima?.proximaProgramable;
return PluriGlassSurface(
glowColor: const Color(0xFFFFB86B).withValues(alpha: 0.28),
child: Row(
@@ -110,7 +111,7 @@ class _PanelProximaAlarma extends StatelessWidget {
? activasSinProxima > 0
? 'Hay $activasSinProxima alarma(s) activas, pero ahora mismo no tienen una fecha futura válida. Revisá fecha, días y vacaciones.'
: 'Creá una alarma y PluriWave calculará la siguiente ejecución automáticamente.'
: '${proxima.nombre} · ${_fechaHora(proxima.proximaEjecucion!)}',
: '${proxima.nombre} · ${_fechaHora(proximaProgramable!)}',
),
],
),
@@ -193,11 +194,11 @@ class _TarjetaAlarma extends StatelessWidget {
],
),
const SizedBox(height: 12),
if (alarma.proximaEjecucion != null)
if (alarma.proximaProgramable != null)
_NoticeLine(
icon: Icons.event_available_rounded,
text:
'Siguiente ejecución: ${_fechaHora(alarma.proximaEjecucion!)}',
'Siguiente ejecución: ${_fechaHora(alarma.proximaProgramable!)}',
)
else
const _NoticeLine(
@@ -231,7 +232,7 @@ class _TarjetaAlarma extends StatelessWidget {
icon: const Icon(Icons.skip_next_rounded),
label: const Text('Omitir siguiente'),
onPressed:
alarma.proximaEjecucion == null
alarma.proximaProgramable == null
? null
: () async {
await estado.saltarProxima(alarma.id);
@@ -248,9 +249,9 @@ class _TarjetaAlarma extends StatelessWidget {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
actualizada?.proximaEjecucion == null
actualizada?.proximaProgramable == null
? 'Alarma omitida. No queda próxima ejecución.'
: 'Alarma omitida. Volverá el ${_fechaHora(actualizada!.proximaEjecucion!)}.',
: 'Alarma omitida. Volverá el ${_fechaHora(actualizada!.proximaProgramable!)}.',
),
),
);
@@ -281,13 +282,13 @@ class _TarjetaAlarma extends StatelessWidget {
}
}
if (actual != null) {
if (alarma.proximaEjecucion == null) {
if (alarma.proximaProgramable == null) {
return 'Está pausada por vacaciones (${actual.nombre}) y sin próxima ejecución.';
}
return 'Está pausada por vacaciones (${actual.nombre}) y vuelve el ${_fechaHora(alarma.proximaEjecucion!)}.';
return 'Está pausada por vacaciones (${actual.nombre}) y vuelve el ${_fechaHora(alarma.proximaProgramable!)}.';
}
if (alarma.proximaEjecucion != null) {
return 'Con vacaciones activas, volverá a sonar el ${_fechaHora(alarma.proximaEjecucion!)}.';
if (alarma.proximaProgramable != null) {
return 'Con vacaciones activas, volverá a sonar el ${_fechaHora(alarma.proximaProgramable!)}.';
}
return null;
}
@@ -690,12 +691,18 @@ class _AccesoDiagnostico extends StatelessWidget {
label: Text(
diag == null
? 'Revisar fiabilidad Android'
: 'Fiabilidad: exactas ${diag.puedeProgramarExactas ? 'OK' : 'pendiente'} · notificaciones ${diag.notificacionesPermitidas ? 'OK' : 'pendiente'}',
: 'Fiabilidad: exactas ${diag.puedeProgramarExactas ? 'OK' : 'pendiente'} ? notificaciones ${diag.notificacionesPermitidas ? 'OK' : 'pendiente'} ? pantalla ${diag.puedeUsarPantallaCompleta ? 'OK' : 'pendiente'}',
),
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();
},
);
+80 -1
View File
@@ -199,6 +199,81 @@ class ServicioAlarmas {
return nuevo;
}
Future<ConfiguracionAlarmas> posponerEjecucion(
String alarmaId,
DateTime ejecucion,
int minutos,
) async {
final snoozeHasta = _programacion.calcularSnooze(_reloj(), minutos);
return posponerEjecucionHasta(alarmaId, ejecucion, snoozeHasta);
}
Future<ConfiguracionAlarmas> posponerEjecucionHasta(
String alarmaId,
DateTime ejecucion,
DateTime snoozeHasta,
) async {
final config = await cargar();
final ahora = _reloj();
final alarmas =
config.alarmas
.map(
(a) =>
a.id == alarmaId
? a.copyWith(
snoozeHasta: snoozeHasta,
snoozeOrigen: ejecucion,
ultimaEjecucionGestionada: ejecucion,
actualizadaEn: ahora,
)
: a,
)
.toList();
final nuevo = ConfiguracionAlarmas(
alarmas: alarmas,
vacaciones: config.vacaciones,
excepciones: config.excepciones,
);
await _guardar(nuevo);
return nuevo;
}
Future<ConfiguracionAlarmas> completarEjecucion(
String alarmaId,
DateTime ejecucion,
) async {
final config = await cargar();
final ahora = _reloj();
final alarmas =
config.alarmas.map((a) {
if (a.id != alarmaId) return a;
final siguiente = _programacion.calcularSiguienteDespuesDeEjecucion(
alarma: a,
ejecucion: ejecucion,
vacaciones: config.vacaciones,
excepciones: config.excepciones,
);
return a.copyWith(
activa:
a.tipoProgramacion == TipoProgramacionAlarma.unica
? false
: a.activa,
proximaEjecucion: siguiente,
limpiarProximaEjecucion: true,
limpiarSnooze: true,
ultimaEjecucionGestionada: ejecucion,
actualizadaEn: ahora,
);
}).toList();
final nuevo = ConfiguracionAlarmas(
alarmas: alarmas,
vacaciones: config.vacaciones,
excepciones: config.excepciones,
);
await _guardar(nuevo);
return nuevo;
}
AlarmaMusical crearAlarma({
required String nombre,
required int hora,
@@ -250,15 +325,19 @@ class ServicioAlarmas {
List<RangoVacaciones> vacaciones,
List<ExcepcionAlarma> excepciones,
) {
final ahora = _reloj();
final snoozeActivo =
alarma.snoozeHasta != null && alarma.snoozeHasta!.isAfter(ahora);
final proxima = _programacion.calcularProxima(
alarma: alarma,
desde: _reloj(),
desde: ahora,
vacaciones: vacaciones,
excepciones: excepciones,
);
return alarma.copyWith(
proximaEjecucion: proxima,
limpiarProximaEjecucion: true,
limpiarSnooze: !snoozeActivo,
);
}
+79 -3
View File
@@ -10,17 +10,26 @@ class EventoAlarmaAndroid {
required this.alarmaId,
required this.titulo,
required this.accion,
this.triggerAtMillis = 0,
this.occurrenceAtMillis = 0,
this.snoozeMinutes = 5,
});
final String alarmaId;
final String titulo;
final String accion;
final int triggerAtMillis;
final int occurrenceAtMillis;
final int snoozeMinutes;
factory EventoAlarmaAndroid.fromMap(Map<Object?, Object?> map) {
return EventoAlarmaAndroid(
alarmaId: map['alarmId'] as String? ?? '',
titulo: map['alarmTitle'] as String? ?? 'PluriWave',
accion: map['alarmAction'] as String? ?? '',
triggerAtMillis: (map['triggerAtMillis'] as num?)?.toInt() ?? 0,
occurrenceAtMillis: (map['occurrenceAtMillis'] as num?)?.toInt() ?? 0,
snoozeMinutes: (map['snoozeMinutes'] as num?)?.toInt() ?? 5,
);
}
}
@@ -29,12 +38,18 @@ class DiagnosticoAlarmasAndroid {
const DiagnosticoAlarmasAndroid({
required this.puedeProgramarExactas,
required this.notificacionesPermitidas,
required this.puedeUsarPantallaCompleta,
required this.ignoraOptimizacionBateria,
required this.alarmasNativasPendientes,
required this.fabricante,
required this.versionSdk,
});
final bool puedeProgramarExactas;
final bool notificacionesPermitidas;
final bool puedeUsarPantallaCompleta;
final bool ignoraOptimizacionBateria;
final int alarmasNativasPendientes;
final String fabricante;
final int versionSdk;
@@ -42,13 +57,33 @@ class DiagnosticoAlarmasAndroid {
return DiagnosticoAlarmasAndroid(
puedeProgramarExactas: map['canScheduleExactAlarms'] as bool? ?? true,
notificacionesPermitidas: map['notificationsEnabled'] as bool? ?? true,
puedeUsarPantallaCompleta:
map['canUseFullScreenIntent'] as bool? ?? true,
ignoraOptimizacionBateria:
map['isIgnoringBatteryOptimizations'] as bool? ?? true,
alarmasNativasPendientes: map['nativePendingAlarmsCount'] as int? ?? 0,
fabricante: map['manufacturer'] as String? ?? 'Android',
versionSdk: map['sdkInt'] as int? ?? 0,
);
}
}
class ServicioAlarmasAndroid {
abstract class PuertoAlarmasAndroid {
Stream<EventoAlarmaAndroid> get eventosAlarma;
Future<void> programar(AlarmaMusical alarma);
Future<void> cancelar(String alarmaId);
Future<void> ocultarNotificacionAlarma(String alarmaId);
Future<void> detenerSonidoNativo(String alarmaId);
Future<bool> solicitarPermisoAlarmasExactas();
Future<bool> solicitarPermisoNotificaciones();
Future<bool> solicitarPermisoPantallaCompleta();
Future<void> confirmarAudioFlutter(String alarmaId);
Future<DiagnosticoAlarmasAndroid> diagnostico();
Future<EventoAlarmaAndroid?> obtenerEventoInicial();
}
class ServicioAlarmasAndroid implements PuertoAlarmasAndroid {
ServicioAlarmasAndroid({
MethodChannel channel = const MethodChannel('pluriwave/alarm_scheduler'),
}) : _channel = channel {
@@ -60,10 +95,12 @@ class ServicioAlarmasAndroid {
StreamController<EventoAlarmaAndroid>.broadcast();
static bool _handlerInstalado = false;
@override
Stream<EventoAlarmaAndroid> get eventosAlarma => _eventosController.stream;
@override
Future<void> programar(AlarmaMusical alarma) async {
final proxima = alarma.proximaEjecucion;
final proxima = alarma.proximaProgramable;
if (proxima == null || !alarma.activa) {
debugPrint(
'[PluriWave][alarmas] cancelar por inactiva/sin proxima id=${alarma.id} activa=${alarma.activa} proxima=$proxima',
@@ -79,7 +116,20 @@ class ServicioAlarmasAndroid {
'title': alarma.nombre,
'triggerAtMillis': proxima.millisecondsSinceEpoch,
'preNoticeAtMillis':
proxima.subtract(const Duration(minutes: 30)).millisecondsSinceEpoch,
alarma.snoozeHasta == null
? proxima.subtract(const Duration(minutes: 30)).millisecondsSinceEpoch
: 0,
'hour': alarma.hora,
'minute': alarma.minuto,
'scheduleType': alarma.tipoProgramacion.name,
'weekdays': alarma.diasSemana,
'oneShotDateMillis': alarma.fechaUnica?.millisecondsSinceEpoch,
'snoozeUntilMillis': alarma.snoozeHasta?.millisecondsSinceEpoch,
'snoozeOriginMillis': alarma.snoozeOrigen?.millisecondsSinceEpoch,
'snoozeMinutes': alarma.snoozeMinutos,
'lastHandledAtMillis':
alarma.ultimaEjecucionGestionada?.millisecondsSinceEpoch,
'soundOnVacation': alarma.sonarEnVacaciones,
'stationName': alarma.emisora?.nombre,
'stationUrl': alarma.emisora?.url,
'fallbackSound': alarma.sonidoInterno.name,
@@ -92,15 +142,23 @@ class ServicioAlarmasAndroid {
}
}
@override
Future<void> cancelar(String alarmaId) =>
_logAndInvokeVoid('cancelAlarm', {'id': alarmaId});
@override
Future<void> ocultarNotificacionAlarma(String alarmaId) =>
_logAndInvokeVoid('dismissAlarmNotification', {'id': alarmaId});
@override
Future<void> detenerSonidoNativo(String alarmaId) =>
_logAndInvokeVoid('stopNativeAlarmSound', {'id': alarmaId});
@override
Future<void> confirmarAudioFlutter(String alarmaId) =>
_logAndInvokeVoid('confirmFlutterAudio', {'id': alarmaId});
@override
Future<bool> solicitarPermisoAlarmasExactas() async {
final abierto = await _channel.invokeMethod<bool>(
'requestExactAlarmPermission',
@@ -108,6 +166,23 @@ class ServicioAlarmasAndroid {
return abierto ?? false;
}
@override
Future<bool> solicitarPermisoNotificaciones() async {
final abierto = await _channel.invokeMethod<bool>(
'requestPostNotificationsPermission',
);
return abierto ?? false;
}
@override
Future<bool> solicitarPermisoPantallaCompleta() async {
final abierto = await _channel.invokeMethod<bool>(
'requestFullScreenIntentPermission',
);
return abierto ?? false;
}
@override
Future<DiagnosticoAlarmasAndroid> diagnostico() async {
debugPrint('[PluriWave][alarmas] diagnostico android');
final raw = await _channel.invokeMethod<Map<Object?, Object?>>(
@@ -120,6 +195,7 @@ class ServicioAlarmasAndroid {
return diag;
}
@override
Future<EventoAlarmaAndroid?> obtenerEventoInicial() async {
final raw = await _channel.invokeMethod<Map<Object?, Object?>>(
'getInitialAlarmIntent',
@@ -58,6 +58,23 @@ class ServicioProgramacionAlarmas {
return desde.add(Duration(minutes: seguro));
}
DateTime? calcularSiguienteDespuesDeEjecucion({
required AlarmaMusical alarma,
required DateTime ejecucion,
List<RangoVacaciones> vacaciones = const [],
List<ExcepcionAlarma> excepciones = const [],
}) {
if (!alarma.activa) return null;
if (alarma.tipoProgramacion == TipoProgramacionAlarma.unica) return null;
return calcularProxima(
alarma: alarma.copyWith(limpiarSnooze: true),
desde: ejecucion.add(const Duration(minutes: 1)),
vacaciones: vacaciones,
excepciones: excepciones,
);
}
bool estaEnVacaciones(DateTime fecha, List<RangoVacaciones> vacaciones) =>
vacaciones.any((rango) => rango.contiene(fecha));