feat(alarm): complete musical alarm flows
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../estado/estado_alarmas.dart';
|
||||
import '../estado/estado_radio.dart';
|
||||
import '../modelos/alarma_musical.dart';
|
||||
import '../servicios/servicio_audio.dart';
|
||||
import '../widgets/pluri_glass_surface.dart';
|
||||
|
||||
class PantallaAlarmaSonando extends StatefulWidget {
|
||||
const PantallaAlarmaSonando({super.key, required this.alarma});
|
||||
|
||||
final AlarmaMusical alarma;
|
||||
|
||||
@override
|
||||
State<PantallaAlarmaSonando> createState() => _PantallaAlarmaSonandoState();
|
||||
}
|
||||
|
||||
class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
|
||||
final AudioPlayer _fallbackPlayer = AudioPlayer();
|
||||
StreamSubscription<EstadoReproduccion>? _estadoSub;
|
||||
Timer? _fallbackTimer;
|
||||
bool _fallbackActivo = false;
|
||||
bool _radioIntentada = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _iniciarAlarma());
|
||||
}
|
||||
|
||||
Future<void> _iniciarAlarma() async {
|
||||
final radio = context.read<EstadoRadio>();
|
||||
await _fallbackPlayer.setVolume(widget.alarma.volumen.clamp(0.0, 1.0));
|
||||
await _fallbackPlayer.setLoopMode(LoopMode.one);
|
||||
|
||||
final emisora = widget.alarma.emisora;
|
||||
if (emisora == null) {
|
||||
await _iniciarFallback();
|
||||
return;
|
||||
}
|
||||
|
||||
_radioIntentada = true;
|
||||
await radio.audio.setVolumen(widget.alarma.volumen.clamp(0.0, 1.0));
|
||||
unawaited(radio.reproducir(emisora));
|
||||
|
||||
_estadoSub = radio.estadoStream.listen((estado) {
|
||||
if (estado == EstadoReproduccion.reproduciendo && mounted) {
|
||||
_fallbackTimer?.cancel();
|
||||
}
|
||||
if (estado == EstadoReproduccion.error && mounted) {
|
||||
_iniciarFallback();
|
||||
}
|
||||
});
|
||||
|
||||
_fallbackTimer = Timer(const Duration(seconds: 12), () {
|
||||
if (mounted) _iniciarFallback();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _iniciarFallback() async {
|
||||
if (_fallbackActivo) return;
|
||||
_fallbackActivo = true;
|
||||
await _fallbackPlayer.setAsset(_assetFallback(widget.alarma.sonidoInterno));
|
||||
await _fallbackPlayer.play();
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> _detener() async {
|
||||
final radio = context.read<EstadoRadio>();
|
||||
final alarmas = context.read<EstadoAlarmas>();
|
||||
final navigator = Navigator.of(context);
|
||||
_fallbackTimer?.cancel();
|
||||
await _estadoSub?.cancel();
|
||||
await _fallbackPlayer.stop();
|
||||
await radio.audio.pausar();
|
||||
await alarmas.finalizarEjecucion(widget.alarma.id);
|
||||
if (mounted) navigator.pop();
|
||||
}
|
||||
|
||||
Future<void> _posponer(int minutos) async {
|
||||
final radio = context.read<EstadoRadio>();
|
||||
final alarmas = context.read<EstadoAlarmas>();
|
||||
final navigator = Navigator.of(context);
|
||||
_fallbackTimer?.cancel();
|
||||
await _estadoSub?.cancel();
|
||||
await _fallbackPlayer.stop();
|
||||
await radio.audio.pausar();
|
||||
await alarmas.posponerAlarma(widget.alarma, minutos);
|
||||
if (mounted) navigator.pop();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fallbackTimer?.cancel();
|
||||
_estadoSub?.cancel();
|
||||
_fallbackPlayer.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final alarma = widget.alarma;
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF061722),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Center(
|
||||
child: PluriGlassSurface(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
padding: const EdgeInsets.all(24),
|
||||
glowColor: const Color(0xFFFFB86B).withValues(alpha: 0.35),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/icons/alarmas/alarm_music.png',
|
||||
width: 128,
|
||||
height: 128,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_hora(alarma),
|
||||
style: Theme.of(context).textTheme.displayMedium?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: -2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
alarma.nombre,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_fallbackActivo
|
||||
? 'Sonando con audio seguro interno.'
|
||||
: _radioIntentada
|
||||
? 'Intentando reproducir tu emisora con máxima calidad disponible.'
|
||||
: 'Preparando audio seguro interno.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
FilledButton.icon(
|
||||
onPressed: _detener,
|
||||
icon: const Icon(Icons.stop_rounded),
|
||||
label: const Text('Detener alarma'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
for (final min in const [3, 5, 10])
|
||||
ActionChip(
|
||||
avatar: const Icon(Icons.snooze_rounded, size: 18),
|
||||
label: Text('Posponer $min min'),
|
||||
onPressed: () => _posponer(min),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _assetFallback(SonidoInternoAlarma sonido) => switch (sonido) {
|
||||
SonidoInternoAlarma.amanecer => 'assets/audio/alarm_amanecer.wav',
|
||||
SonidoInternoAlarma.campanaSuave =>
|
||||
'assets/audio/alarm_campana_suave.wav',
|
||||
SonidoInternoAlarma.pulsoDigital => 'assets/audio/alarm_pulso_digital.wav',
|
||||
};
|
||||
|
||||
String _hora(AlarmaMusical alarma) =>
|
||||
'${alarma.hora.toString().padLeft(2, '0')}:${alarma.minuto.toString().padLeft(2, '0')}';
|
||||
+818
-131
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user