feat(alarmas): agregar fade-in configurable en activacion
This commit is contained in:
+2
-1
@@ -58,6 +58,7 @@ class _PaginaPrincipal extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PaginaPrincipalState extends State<_PaginaPrincipal> {
|
class _PaginaPrincipalState extends State<_PaginaPrincipal> {
|
||||||
|
static const _volumenInicialFadeInAlarmas = 0.05;
|
||||||
int _indice = 0;
|
int _indice = 0;
|
||||||
StreamSubscription<String>? _errorSubscription;
|
StreamSubscription<String>? _errorSubscription;
|
||||||
StreamSubscription<EventoAlarmaAndroid>? _alarmaSubscription;
|
StreamSubscription<EventoAlarmaAndroid>? _alarmaSubscription;
|
||||||
@@ -316,7 +317,7 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
|
|||||||
debugPrint(
|
debugPrint(
|
||||||
'[PluriWave][alarmas] prearrancar emisora alarma id=${alarma.id} emisora=${emisora.nombre}',
|
'[PluriWave][alarmas] prearrancar emisora alarma id=${alarma.id} emisora=${emisora.nombre}',
|
||||||
);
|
);
|
||||||
await radio.audio.setVolumen(alarma.volumen.clamp(0.0, 1.0));
|
await radio.audio.setVolumen(_volumenInicialFadeInAlarmas);
|
||||||
unawaited(radio.reproducir(emisora));
|
unawaited(radio.reproducir(emisora));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class AlarmaMusical {
|
|||||||
this.sonarEnVacaciones = true,
|
this.sonarEnVacaciones = true,
|
||||||
this.snoozeMinutos = 5,
|
this.snoozeMinutos = 5,
|
||||||
this.volumen = 0.85,
|
this.volumen = 0.85,
|
||||||
|
this.fadeInSegundos = 0,
|
||||||
this.sonidoInterno = SonidoInternoAlarma.amanecer,
|
this.sonidoInterno = SonidoInternoAlarma.amanecer,
|
||||||
this.proximaEjecucion,
|
this.proximaEjecucion,
|
||||||
this.snoozeHasta,
|
this.snoozeHasta,
|
||||||
@@ -41,6 +42,7 @@ class AlarmaMusical {
|
|||||||
final bool sonarEnVacaciones;
|
final bool sonarEnVacaciones;
|
||||||
final int snoozeMinutos;
|
final int snoozeMinutos;
|
||||||
final double volumen;
|
final double volumen;
|
||||||
|
final int fadeInSegundos;
|
||||||
final SonidoInternoAlarma sonidoInterno;
|
final SonidoInternoAlarma sonidoInterno;
|
||||||
final DateTime? proximaEjecucion;
|
final DateTime? proximaEjecucion;
|
||||||
final DateTime? snoozeHasta;
|
final DateTime? snoozeHasta;
|
||||||
@@ -64,6 +66,7 @@ class AlarmaMusical {
|
|||||||
bool? sonarEnVacaciones,
|
bool? sonarEnVacaciones,
|
||||||
int? snoozeMinutos,
|
int? snoozeMinutos,
|
||||||
double? volumen,
|
double? volumen,
|
||||||
|
int? fadeInSegundos,
|
||||||
SonidoInternoAlarma? sonidoInterno,
|
SonidoInternoAlarma? sonidoInterno,
|
||||||
DateTime? proximaEjecucion,
|
DateTime? proximaEjecucion,
|
||||||
bool limpiarProximaEjecucion = false,
|
bool limpiarProximaEjecucion = false,
|
||||||
@@ -89,6 +92,7 @@ class AlarmaMusical {
|
|||||||
sonarEnVacaciones: sonarEnVacaciones ?? this.sonarEnVacaciones,
|
sonarEnVacaciones: sonarEnVacaciones ?? this.sonarEnVacaciones,
|
||||||
snoozeMinutos: snoozeMinutos ?? this.snoozeMinutos,
|
snoozeMinutos: snoozeMinutos ?? this.snoozeMinutos,
|
||||||
volumen: volumen ?? this.volumen,
|
volumen: volumen ?? this.volumen,
|
||||||
|
fadeInSegundos: fadeInSegundos ?? this.fadeInSegundos,
|
||||||
sonidoInterno: sonidoInterno ?? this.sonidoInterno,
|
sonidoInterno: sonidoInterno ?? this.sonidoInterno,
|
||||||
proximaEjecucion:
|
proximaEjecucion:
|
||||||
limpiarProximaEjecucion
|
limpiarProximaEjecucion
|
||||||
@@ -122,6 +126,7 @@ class AlarmaMusical {
|
|||||||
'sonarEnVacaciones': sonarEnVacaciones,
|
'sonarEnVacaciones': sonarEnVacaciones,
|
||||||
'snoozeMinutos': snoozeMinutos,
|
'snoozeMinutos': snoozeMinutos,
|
||||||
'volumen': volumen,
|
'volumen': volumen,
|
||||||
|
'fadeInSegundos': fadeInSegundos,
|
||||||
'sonidoInterno': sonidoInterno.name,
|
'sonidoInterno': sonidoInterno.name,
|
||||||
'proximaEjecucion': proximaEjecucion?.toIso8601String(),
|
'proximaEjecucion': proximaEjecucion?.toIso8601String(),
|
||||||
'snoozeHasta': snoozeHasta?.toIso8601String(),
|
'snoozeHasta': snoozeHasta?.toIso8601String(),
|
||||||
@@ -154,6 +159,8 @@ class AlarmaMusical {
|
|||||||
sonarEnVacaciones: json['sonarEnVacaciones'] as bool? ?? true,
|
sonarEnVacaciones: json['sonarEnVacaciones'] as bool? ?? true,
|
||||||
snoozeMinutos: json['snoozeMinutos'] as int? ?? 5,
|
snoozeMinutos: json['snoozeMinutos'] as int? ?? 5,
|
||||||
volumen: (json['volumen'] as num?)?.toDouble() ?? 0.85,
|
volumen: (json['volumen'] as num?)?.toDouble() ?? 0.85,
|
||||||
|
fadeInSegundos: ((json['fadeInSegundos'] as int? ?? 0).clamp(0, 60))
|
||||||
|
as int,
|
||||||
sonidoInterno: _enumFromName(
|
sonidoInterno: _enumFromName(
|
||||||
SonidoInternoAlarma.values,
|
SonidoInternoAlarma.values,
|
||||||
json['sonidoInterno'] as String?,
|
json['sonidoInterno'] as String?,
|
||||||
|
|||||||
@@ -25,9 +25,12 @@ class PantallaAlarmaSonando extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
|
class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
|
||||||
|
static const _volumenInicialFadeIn = 0.05;
|
||||||
|
static const _fadeStep = Duration(milliseconds: 250);
|
||||||
final AudioPlayer _fallbackPlayer = AudioPlayer();
|
final AudioPlayer _fallbackPlayer = AudioPlayer();
|
||||||
StreamSubscription<EstadoReproduccion>? _estadoSub;
|
StreamSubscription<EstadoReproduccion>? _estadoSub;
|
||||||
Timer? _fallbackTimer;
|
Timer? _fallbackTimer;
|
||||||
|
Timer? _fadeInTimer;
|
||||||
bool _fallbackActivo = false;
|
bool _fallbackActivo = false;
|
||||||
bool _radioIntentada = false;
|
bool _radioIntentada = false;
|
||||||
bool _audioFlutterConfirmado = false;
|
bool _audioFlutterConfirmado = false;
|
||||||
@@ -40,7 +43,7 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
|
|||||||
|
|
||||||
Future<void> _iniciarAlarma() async {
|
Future<void> _iniciarAlarma() async {
|
||||||
final radio = context.read<EstadoRadio>();
|
final radio = context.read<EstadoRadio>();
|
||||||
await _fallbackPlayer.setVolume(widget.alarma.volumen.clamp(0.0, 1.0));
|
await _fallbackPlayer.setVolume(_volumenInicialFadeIn);
|
||||||
await _fallbackPlayer.setLoopMode(LoopMode.one);
|
await _fallbackPlayer.setLoopMode(LoopMode.one);
|
||||||
|
|
||||||
final emisora = widget.alarma.emisora;
|
final emisora = widget.alarma.emisora;
|
||||||
@@ -50,10 +53,11 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_radioIntentada = true;
|
_radioIntentada = true;
|
||||||
await radio.audio.setVolumen(widget.alarma.volumen.clamp(0.0, 1.0));
|
await radio.audio.setVolumen(_volumenInicialFadeIn);
|
||||||
if (!widget.audioPrearrancado) {
|
if (!widget.audioPrearrancado) {
|
||||||
unawaited(radio.reproducir(emisora));
|
unawaited(radio.reproducir(emisora));
|
||||||
}
|
}
|
||||||
|
_iniciarFadeIn();
|
||||||
|
|
||||||
_estadoSub = radio.estadoStream.listen((estado) {
|
_estadoSub = radio.estadoStream.listen((estado) {
|
||||||
if (estado == EstadoReproduccion.reproduciendo && mounted) {
|
if (estado == EstadoReproduccion.reproduciendo && mounted) {
|
||||||
@@ -78,10 +82,43 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
|
|||||||
_fallbackActivo = true;
|
_fallbackActivo = true;
|
||||||
await _fallbackPlayer.setAsset(_assetFallback(widget.alarma.sonidoInterno));
|
await _fallbackPlayer.setAsset(_assetFallback(widget.alarma.sonidoInterno));
|
||||||
await _fallbackPlayer.play();
|
await _fallbackPlayer.play();
|
||||||
|
_iniciarFadeIn();
|
||||||
await _confirmarAudioFlutterListo();
|
await _confirmarAudioFlutterListo();
|
||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _iniciarFadeIn() {
|
||||||
|
_fadeInTimer?.cancel();
|
||||||
|
final volumenObjetivo = widget.alarma.volumen.clamp(0.0, 1.0);
|
||||||
|
final inicio = _volumenInicialFadeIn.clamp(0.0, volumenObjetivo);
|
||||||
|
final segundosFade = widget.alarma.fadeInSegundos.clamp(0, 60);
|
||||||
|
if (segundosFade <= 0 || volumenObjetivo <= inicio) {
|
||||||
|
unawaited(_aplicarVolumenGlobal(volumenObjetivo));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final duracionTotalMs = segundosFade * 1000;
|
||||||
|
final pasos = (duracionTotalMs / _fadeStep.inMilliseconds).ceil();
|
||||||
|
var pasoActual = 0;
|
||||||
|
_fadeInTimer = Timer.periodic(_fadeStep, (timer) {
|
||||||
|
if (!mounted) {
|
||||||
|
timer.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pasoActual++;
|
||||||
|
final t = (pasoActual / pasos).clamp(0.0, 1.0);
|
||||||
|
final volumenActual = inicio + (volumenObjetivo - inicio) * t;
|
||||||
|
unawaited(_aplicarVolumenGlobal(volumenActual));
|
||||||
|
if (t >= 1) timer.cancel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _aplicarVolumenGlobal(double volumen) async {
|
||||||
|
if (!mounted) return;
|
||||||
|
final radio = context.read<EstadoRadio>();
|
||||||
|
await radio.audio.setVolumen(volumen.clamp(0.0, 1.0));
|
||||||
|
await _fallbackPlayer.setVolume(volumen.clamp(0.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _confirmarAudioFlutterListo() async {
|
Future<void> _confirmarAudioFlutterListo() async {
|
||||||
if (_audioFlutterConfirmado) return;
|
if (_audioFlutterConfirmado) return;
|
||||||
_audioFlutterConfirmado = true;
|
_audioFlutterConfirmado = true;
|
||||||
@@ -95,6 +132,7 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
|
|||||||
final alarmas = context.read<EstadoAlarmas>();
|
final alarmas = context.read<EstadoAlarmas>();
|
||||||
final navigator = Navigator.of(context);
|
final navigator = Navigator.of(context);
|
||||||
_fallbackTimer?.cancel();
|
_fallbackTimer?.cancel();
|
||||||
|
_fadeInTimer?.cancel();
|
||||||
await _estadoSub?.cancel();
|
await _estadoSub?.cancel();
|
||||||
await _fallbackPlayer.stop();
|
await _fallbackPlayer.stop();
|
||||||
await radio.audio.pausar();
|
await radio.audio.pausar();
|
||||||
@@ -107,6 +145,7 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
|
|||||||
final alarmas = context.read<EstadoAlarmas>();
|
final alarmas = context.read<EstadoAlarmas>();
|
||||||
final navigator = Navigator.of(context);
|
final navigator = Navigator.of(context);
|
||||||
_fallbackTimer?.cancel();
|
_fallbackTimer?.cancel();
|
||||||
|
_fadeInTimer?.cancel();
|
||||||
await _estadoSub?.cancel();
|
await _estadoSub?.cancel();
|
||||||
await _fallbackPlayer.stop();
|
await _fallbackPlayer.stop();
|
||||||
await radio.audio.pausar();
|
await radio.audio.pausar();
|
||||||
@@ -117,6 +156,7 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_fallbackTimer?.cancel();
|
_fallbackTimer?.cancel();
|
||||||
|
_fadeInTimer?.cancel();
|
||||||
_estadoSub?.cancel();
|
_estadoSub?.cancel();
|
||||||
_fallbackPlayer.dispose();
|
_fallbackPlayer.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|||||||
@@ -191,6 +191,10 @@ class _TarjetaAlarma extends StatelessWidget {
|
|||||||
icon: Icons.volume_up_rounded,
|
icon: Icons.volume_up_rounded,
|
||||||
label: '${(alarma.volumen * 100).round()}%',
|
label: '${(alarma.volumen * 100).round()}%',
|
||||||
),
|
),
|
||||||
|
_InfoChip(
|
||||||
|
icon: Icons.trending_up_rounded,
|
||||||
|
label: 'Fade-in ${alarma.fadeInSegundos}s',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@@ -321,6 +325,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
|
|||||||
late Set<int> _diasSemana;
|
late Set<int> _diasSemana;
|
||||||
late int _snooze;
|
late int _snooze;
|
||||||
late double _volumen;
|
late double _volumen;
|
||||||
|
late int _fadeInSegundos;
|
||||||
late bool _sonarEnVacaciones;
|
late bool _sonarEnVacaciones;
|
||||||
late SonidoInternoAlarma _sonidoInterno;
|
late SonidoInternoAlarma _sonidoInterno;
|
||||||
Emisora? _emisora;
|
Emisora? _emisora;
|
||||||
@@ -343,6 +348,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
|
|||||||
_diasSemana = {...alarma?.diasSemana ?? const <int>[]};
|
_diasSemana = {...alarma?.diasSemana ?? const <int>[]};
|
||||||
_snooze = alarma?.snoozeMinutos ?? 5;
|
_snooze = alarma?.snoozeMinutos ?? 5;
|
||||||
_volumen = alarma?.volumen ?? 0.85;
|
_volumen = alarma?.volumen ?? 0.85;
|
||||||
|
_fadeInSegundos = ((alarma?.fadeInSegundos ?? 0).clamp(0, 60)) as int;
|
||||||
_sonarEnVacaciones = alarma?.sonarEnVacaciones ?? true;
|
_sonarEnVacaciones = alarma?.sonarEnVacaciones ?? true;
|
||||||
_sonidoInterno = alarma?.sonidoInterno ?? SonidoInternoAlarma.amanecer;
|
_sonidoInterno = alarma?.sonidoInterno ?? SonidoInternoAlarma.amanecer;
|
||||||
_emisora = alarma?.emisora ?? context.read<EstadoRadio>().emisoraPreferida;
|
_emisora = alarma?.emisora ?? context.read<EstadoRadio>().emisoraPreferida;
|
||||||
@@ -502,6 +508,26 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
|
|||||||
label: '${(_volumen * 100).round()}%',
|
label: '${(_volumen * 100).round()}%',
|
||||||
onChanged: (value) => setState(() => _volumen = value),
|
onChanged: (value) => setState(() => _volumen = value),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
ListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
title: const Text('Fade-in de alarma'),
|
||||||
|
subtitle: Text(
|
||||||
|
_fadeInSegundos == 0
|
||||||
|
? '0 s (sin transición)'
|
||||||
|
: '$_fadeInSegundos s (de 5% al volumen elegido)',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Slider(
|
||||||
|
value: _fadeInSegundos.toDouble(),
|
||||||
|
min: 0,
|
||||||
|
max: 60,
|
||||||
|
divisions: 60,
|
||||||
|
label: '${_fadeInSegundos}s',
|
||||||
|
onChanged:
|
||||||
|
(value) =>
|
||||||
|
setState(() => _fadeInSegundos = value.round()),
|
||||||
|
),
|
||||||
DropdownButtonFormField<SonidoInternoAlarma>(
|
DropdownButtonFormField<SonidoInternoAlarma>(
|
||||||
initialValue: _sonidoInterno,
|
initialValue: _sonidoInterno,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
@@ -655,6 +681,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
|
|||||||
sonarEnVacaciones: _sonarEnVacaciones,
|
sonarEnVacaciones: _sonarEnVacaciones,
|
||||||
snoozeMinutos: _snooze,
|
snoozeMinutos: _snooze,
|
||||||
volumen: _volumen,
|
volumen: _volumen,
|
||||||
|
fadeInSegundos: _fadeInSegundos.clamp(0, 60),
|
||||||
sonidoInterno: _sonidoInterno,
|
sonidoInterno: _sonidoInterno,
|
||||||
activa: true,
|
activa: true,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user