feat(alarms): add native ringing service
This commit is contained in:
+90
-88
@@ -214,9 +214,9 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).skipCurrentAlarmExecution(
|
||||
alarma.nombre,
|
||||
),
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
).skipCurrentAlarmExecution(alarma.nombre),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -249,6 +249,7 @@ 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(
|
||||
@@ -286,89 +287,96 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
|
||||
showDragHandle: true,
|
||||
builder:
|
||||
(ctx) => Consumer<EstadoRadio>(
|
||||
builder: (ctx, estado, _) => SafeArea(
|
||||
child: Padding(
|
||||
padding: PluriLayout.sheetPadding,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(ctx).sleepTimer,
|
||||
style: Theme.of(ctx).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: PluriLayout.sectionGap),
|
||||
Text(
|
||||
AppLocalizations.of(ctx).sleepTimerDescription,
|
||||
style: Theme.of(ctx).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: PluriLayout.panelGap),
|
||||
if (estado.timer.activo)
|
||||
StreamBuilder<Duration>(
|
||||
stream: estado.timer.tiempoRestanteStream,
|
||||
builder: (ctx, snap) {
|
||||
final restante =
|
||||
snap.data ?? estado.timer.tiempoRestante;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
builder:
|
||||
(ctx, estado, _) => SafeArea(
|
||||
child: Padding(
|
||||
padding: PluriLayout.sheetPadding,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(ctx).sleepTimer,
|
||||
style: Theme.of(ctx).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: PluriLayout.sectionGap),
|
||||
Text(
|
||||
AppLocalizations.of(ctx).sleepTimerDescription,
|
||||
style: Theme.of(ctx).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: PluriLayout.panelGap),
|
||||
if (estado.timer.activo)
|
||||
StreamBuilder<Duration>(
|
||||
stream: estado.timer.tiempoRestanteStream,
|
||||
builder: (ctx, snap) {
|
||||
final restante =
|
||||
snap.data ?? estado.timer.tiempoRestante;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
_formatearDuracionTimer(restante),
|
||||
style:
|
||||
Theme.of(ctx).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(
|
||||
height: PluriLayout.compactGap,
|
||||
),
|
||||
FilledButton.tonal(
|
||||
onPressed: () {
|
||||
estado.cancelarTimer();
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(ctx).cancelTimer,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
Wrap(
|
||||
spacing: PluriLayout.compactGap,
|
||||
runSpacing: PluriLayout.compactGap,
|
||||
children: [
|
||||
Text(
|
||||
_formatearDuracionTimer(restante),
|
||||
style: Theme.of(ctx).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: PluriLayout.compactGap),
|
||||
FilledButton.tonal(
|
||||
onPressed: () {
|
||||
estado.cancelarTimer();
|
||||
for (final segundos
|
||||
in estado.timerSuenoPresetsSegundos)
|
||||
ActionChip(
|
||||
label: Text(
|
||||
_formatearDuracionTimer(
|
||||
Duration(seconds: segundos),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
estado.iniciarTimerDuracion(
|
||||
Duration(seconds: segundos),
|
||||
);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
ActionChip(
|
||||
avatar: const Icon(
|
||||
Icons.tune_rounded,
|
||||
size: 18,
|
||||
),
|
||||
label: Text(
|
||||
AppLocalizations.of(ctx).optionOther,
|
||||
),
|
||||
onPressed: () async {
|
||||
final duracion =
|
||||
await _pedirDuracionPersonalizada(ctx);
|
||||
if (duracion == null || !ctx.mounted) return;
|
||||
estado.iniciarTimerDuracion(duracion);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(ctx).cancelTimer,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
Wrap(
|
||||
spacing: PluriLayout.compactGap,
|
||||
runSpacing: PluriLayout.compactGap,
|
||||
children: [
|
||||
for (final segundos
|
||||
in estado.timerSuenoPresetsSegundos)
|
||||
ActionChip(
|
||||
label: Text(
|
||||
_formatearDuracionTimer(
|
||||
Duration(seconds: segundos),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
estado.iniciarTimerDuracion(
|
||||
Duration(seconds: segundos),
|
||||
);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
ActionChip(
|
||||
avatar: const Icon(Icons.tune_rounded, size: 18),
|
||||
label: Text(
|
||||
AppLocalizations.of(ctx).optionOther,
|
||||
),
|
||||
onPressed: () async {
|
||||
final duracion =
|
||||
await _pedirDuracionPersonalizada(ctx);
|
||||
if (duracion == null || !ctx.mounted) return;
|
||||
estado.iniciarTimerDuracion(duracion);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -429,9 +437,7 @@ class _TimerPersonalizadoSheetState extends State<_TimerPersonalizadoSheet> {
|
||||
if (duracion <= Duration.zero) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context).durationGreaterThanZero,
|
||||
),
|
||||
content: Text(AppLocalizations.of(context).durationGreaterThanZero),
|
||||
),
|
||||
);
|
||||
return;
|
||||
@@ -484,18 +490,14 @@ class _TimerPersonalizadoSheetState extends State<_TimerPersonalizadoSheet> {
|
||||
const SizedBox(height: PluriLayout.compactGap),
|
||||
SwitchListTile.adaptive(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
AppLocalizations.of(context).saveQuickAccess,
|
||||
),
|
||||
title: Text(AppLocalizations.of(context).saveQuickAccess),
|
||||
value: _guardarPreset,
|
||||
onChanged: (value) => setState(() => _guardarPreset = value),
|
||||
),
|
||||
const SizedBox(height: PluriLayout.sectionGap),
|
||||
FilledButton.icon(
|
||||
icon: const Icon(Icons.bedtime_rounded),
|
||||
label: Text(
|
||||
AppLocalizations.of(context).startTimer,
|
||||
),
|
||||
label: Text(AppLocalizations.of(context).startTimer),
|
||||
onPressed: _confirmar,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -80,6 +80,10 @@ class ServicioAlarmasAndroid {
|
||||
'triggerAtMillis': proxima.millisecondsSinceEpoch,
|
||||
'preNoticeAtMillis':
|
||||
proxima.subtract(const Duration(minutes: 30)).millisecondsSinceEpoch,
|
||||
'stationName': alarma.emisora?.nombre,
|
||||
'stationUrl': alarma.emisora?.url,
|
||||
'fallbackSound': alarma.sonidoInterno.name,
|
||||
'volume': alarma.volumen,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,6 +93,9 @@ class ServicioAlarmasAndroid {
|
||||
Future<void> ocultarNotificacionAlarma(String alarmaId) =>
|
||||
_logAndInvokeVoid('dismissAlarmNotification', {'id': alarmaId});
|
||||
|
||||
Future<void> detenerSonidoNativo(String alarmaId) =>
|
||||
_logAndInvokeVoid('stopNativeAlarmSound', {'id': alarmaId});
|
||||
|
||||
Future<DiagnosticoAlarmasAndroid> diagnostico() async {
|
||||
debugPrint('[PluriWave][alarmas] diagnostico android');
|
||||
final raw = await _channel.invokeMethod<Map<Object?, Object?>>(
|
||||
|
||||
Reference in New Issue
Block a user