diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 2479a05..51a6655 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -49,6 +49,11 @@
+
+
("title") ?: "PluriWave"
val triggerAtMillis = call.argument("triggerAtMillis")
val preNoticeAtMillis = call.argument("preNoticeAtMillis") ?: 0L
+ val stationName = call.argument("stationName")
+ val stationUrl = call.argument("stationUrl")
+ val fallbackSound = call.argument("fallbackSound")
+ val volume = call.argument("volume")?.toFloat() ?: 0.85f
Log.d(tag, "alarm.channel scheduleAlarm id=$id triggerAtMillis=$triggerAtMillis preNoticeAtMillis=$preNoticeAtMillis")
if (id == null || triggerAtMillis == null) {
Log.w(tag, "alarm.channel scheduleAlarm invalid id=$id triggerAtMillis=$triggerAtMillis")
result.error("INVALID_ALARM", "Missing alarm id or trigger time", null)
} else {
- alarmScheduler.scheduleAlarm(id, title, triggerAtMillis, preNoticeAtMillis)
+ alarmScheduler.scheduleAlarm(
+ id,
+ title,
+ triggerAtMillis,
+ preNoticeAtMillis,
+ stationName,
+ stationUrl,
+ fallbackSound,
+ volume
+ )
result.success(null)
}
}
@@ -79,6 +92,7 @@ class MainActivity : AudioServiceActivity() {
if (id == null) {
result.error("INVALID_ALARM", "Missing alarm id", null)
} else {
+ PluriWaveAlarmService.stop(this, id)
alarmScheduler.cancelAlarm(id)
result.success(null)
}
@@ -89,10 +103,21 @@ class MainActivity : AudioServiceActivity() {
if (id == null) {
result.error("INVALID_ALARM", "Missing alarm id", null)
} else {
+ PluriWaveAlarmService.stop(this, id)
alarmScheduler.dismissFireNotification(id)
result.success(null)
}
}
+ "stopNativeAlarmSound" -> {
+ val id = call.argument("id")
+ Log.d(tag, "alarm.channel stopNativeAlarmSound id=$id")
+ if (id == null) {
+ result.error("INVALID_ALARM", "Missing alarm id", null)
+ } else {
+ PluriWaveAlarmService.stop(this, id)
+ result.success(null)
+ }
+ }
"diagnostics" -> {
Log.d(tag, "alarm.channel diagnostics")
result.success(
diff --git a/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmReceiver.kt b/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmReceiver.kt
index 7394356..a22d6ea 100644
--- a/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmReceiver.kt
+++ b/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmReceiver.kt
@@ -22,6 +22,7 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
when (intent.action) {
ACTION_FIRE -> {
+ PluriWaveAlarmService.start(context, intent)
val launch = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtra(EXTRA_ALARM_ID, alarmId)
@@ -187,6 +188,10 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
const val EXTRA_ALARM_ID = "alarmId"
const val EXTRA_ALARM_TITLE = "alarmTitle"
const val EXTRA_ALARM_ACTION = "alarmAction"
+ const val EXTRA_STATION_NAME = "stationName"
+ const val EXTRA_STATION_URL = "stationUrl"
+ const val EXTRA_FALLBACK_SOUND = "fallbackSound"
+ const val EXTRA_VOLUME = "volume"
fun notificationIdForAlarm(alarmId: String): Int = 53 * alarmId.hashCode() + 7
fun fireNotificationIdForAlarm(alarmId: String): Int = 59 * alarmId.hashCode() + 9
diff --git a/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmService.kt b/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmService.kt
new file mode 100644
index 0000000..c64e7bb
--- /dev/null
+++ b/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmService.kt
@@ -0,0 +1,260 @@
+package es.freetimelab.pluriwave
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.media.AudioAttributes
+import android.media.MediaPlayer
+import android.os.Build
+import android.os.IBinder
+import android.os.PowerManager
+import android.util.Log
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import androidx.core.content.ContextCompat
+import java.io.File
+
+class PluriWaveAlarmService : Service() {
+ private var player: MediaPlayer? = null
+ private var wakeLock: PowerManager.WakeLock? = null
+ private var activeAlarmId: String? = null
+
+ override fun onBind(intent: Intent?): IBinder? = null
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ val action = intent?.action
+ val requestedId = intent?.getStringExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ID)
+ Log.d(TAG, "alarm.service onStartCommand action=$action id=$requestedId active=$activeAlarmId")
+
+ when (action) {
+ ACTION_STOP -> {
+ stopAlarm(requestedId)
+ return START_NOT_STICKY
+ }
+ PluriWaveAlarmReceiver.ACTION_FIRE, null -> startAlarm(intent)
+ else -> Log.w(TAG, "alarm.service unknown action=$action id=$requestedId")
+ }
+ return START_NOT_STICKY
+ }
+
+ private fun startAlarm(intent: Intent?) {
+ val alarmId = intent?.getStringExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ID) ?: return
+ if (activeAlarmId != null) {
+ Log.w(TAG, "alarm.service ignored id=$alarmId because active=$activeAlarmId")
+ return
+ }
+ activeAlarmId = alarmId
+
+ val title = intent.getStringExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_TITLE) ?: "PluriWave"
+ val fallbackSound = intent.getStringExtra(PluriWaveAlarmReceiver.EXTRA_FALLBACK_SOUND)
+ val volume = intent.getFloatExtra(PluriWaveAlarmReceiver.EXTRA_VOLUME, 0.85f).coerceIn(0f, 1f)
+
+ acquireWakeLock()
+ startForeground(NOTIFICATION_ID, buildNotification(alarmId, title))
+ startAudio(alarmId, fallbackSound, volume)
+ }
+
+ private fun startAudio(alarmId: String, fallbackSound: String?, volume: Float) {
+ player?.release()
+ player = null
+
+ val source = fallbackAssetPath(fallbackSound)
+ try {
+ player = MediaPlayer().apply {
+ setAudioAttributes(
+ AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ALARM)
+ .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+ .build()
+ )
+ isLooping = true
+ setVolume(volume, volume)
+ setFallbackAssetDataSource(this, fallbackSound)
+ setOnPreparedListener {
+ it.start()
+ Log.d(TAG, "alarm.service audio started id=$alarmId source=$source")
+ }
+ setOnErrorListener { mp, what, extra ->
+ Log.e(TAG, "alarm.service audio error id=$alarmId what=$what extra=$extra source=$source")
+ mp.reset()
+ true
+ }
+ prepareAsync()
+ }
+ Log.d(TAG, "alarm.service audio preparing id=$alarmId source=$source")
+ } catch (error: Throwable) {
+ Log.e(TAG, "alarm.service audio prepare failed id=$alarmId source=$source", error)
+ }
+ }
+
+ private fun stopAlarm(alarmId: String?) {
+ Log.d(TAG, "alarm.service stop id=$alarmId active=$activeAlarmId")
+ try {
+ player?.stop()
+ } catch (error: Throwable) {
+ Log.w(TAG, "alarm.service stop player failed", error)
+ }
+ player?.release()
+ player = null
+ activeAlarmId = null
+ releaseWakeLock()
+ if (alarmId != null) {
+ NotificationManagerCompat.from(this).cancel(
+ PluriWaveAlarmReceiver.fireNotificationIdForAlarm(alarmId)
+ )
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ stopForeground(STOP_FOREGROUND_REMOVE)
+ } else {
+ @Suppress("DEPRECATION")
+ stopForeground(true)
+ }
+ stopSelf()
+ }
+
+ private fun buildNotification(alarmId: String, title: String) =
+ NotificationCompat.Builder(this, CHANNEL_ID)
+ .setSmallIcon(android.R.drawable.ic_lock_idle_alarm)
+ .setContentTitle("Alarma PluriWave")
+ .setContentText(title)
+ .setCategory(NotificationCompat.CATEGORY_ALARM)
+ .setPriority(NotificationCompat.PRIORITY_MAX)
+ .setOngoing(true)
+ .setAutoCancel(false)
+ .setFullScreenIntent(openAlarmPendingIntent(alarmId, title), true)
+ .setContentIntent(openAlarmPendingIntent(alarmId, title))
+ .addAction(0, "Detener", stopPendingIntent(alarmId))
+ .build()
+
+ private fun openAlarmPendingIntent(alarmId: String, title: String): PendingIntent =
+ PendingIntent.getActivity(
+ this,
+ requestCode(alarmId, 20),
+ Intent(this, MainActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ID, alarmId)
+ putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_TITLE, title)
+ putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ACTION, PluriWaveAlarmReceiver.ACTION_FIRE)
+ },
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ private fun stopPendingIntent(alarmId: String): PendingIntent =
+ PendingIntent.getService(
+ this,
+ requestCode(alarmId, 21),
+ Intent(this, PluriWaveAlarmService::class.java).apply {
+ action = ACTION_STOP
+ putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ID, alarmId)
+ },
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ private fun acquireWakeLock() {
+ if (wakeLock?.isHeld == true) return
+ val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
+ wakeLock = powerManager.newWakeLock(
+ PowerManager.PARTIAL_WAKE_LOCK,
+ "PluriWave:AlarmWakeLock"
+ ).apply {
+ setReferenceCounted(false)
+ acquire(10 * 60 * 1000L)
+ }
+ }
+
+ private fun releaseWakeLock() {
+ try {
+ if (wakeLock?.isHeld == true) wakeLock?.release()
+ } catch (error: Throwable) {
+ Log.w(TAG, "alarm.service wakeLock release failed", error)
+ }
+ wakeLock = null
+ }
+
+ private fun setFallbackAssetDataSource(mediaPlayer: MediaPlayer, sound: String?) {
+ val path = fallbackAssetPath(sound)
+ try {
+ val descriptor = assets.openFd(path)
+ mediaPlayer.setDataSource(
+ descriptor.fileDescriptor,
+ descriptor.startOffset,
+ descriptor.length
+ )
+ descriptor.close()
+ } catch (error: Throwable) {
+ Log.w(TAG, "alarm.service asset descriptor failed path=$path; copying to cache", error)
+ val cached = File(cacheDir, path.substringAfterLast('/'))
+ assets.open(path).use { input ->
+ cached.outputStream().use { output -> input.copyTo(output) }
+ }
+ mediaPlayer.setDataSource(cached.absolutePath)
+ }
+ }
+
+ private fun fallbackAssetPath(sound: String?): String {
+ val fileName = when (sound) {
+ "campanaSuave" -> "alarm_campana_suave.wav"
+ "pulsoDigital" -> "alarm_pulso_digital.wav"
+ else -> "alarm_amanecer.wav"
+ }
+ return "flutter_assets/assets/audio/$fileName"
+ }
+
+ override fun onDestroy() {
+ stopAlarm(activeAlarmId)
+ super.onDestroy()
+ }
+
+ companion object {
+ private const val TAG = "PluriWave"
+ private const val CHANNEL_ID = "pluriwave_alarm_native"
+ private const val NOTIFICATION_ID = 92841
+ private const val ACTION_STOP = "es.freetimelab.pluriwave.alarm.STOP_NATIVE"
+
+ fun start(context: Context, source: Intent) {
+ ensureChannel(context)
+ val intent = Intent(context, PluriWaveAlarmService::class.java).apply {
+ action = PluriWaveAlarmReceiver.ACTION_FIRE
+ putExtras(source)
+ }
+ try {
+ ContextCompat.startForegroundService(context, intent)
+ Log.d(TAG, "alarm.service start requested")
+ } catch (error: Throwable) {
+ Log.e(TAG, "alarm.service start failed", error)
+ }
+ }
+
+ fun stop(context: Context, alarmId: String) {
+ val intent = Intent(context, PluriWaveAlarmService::class.java).apply {
+ action = ACTION_STOP
+ putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ID, alarmId)
+ }
+ try {
+ context.startService(intent)
+ } catch (error: Throwable) {
+ Log.e(TAG, "alarm.service stop request failed id=$alarmId", error)
+ }
+ }
+
+ private fun ensureChannel(context: Context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
+ val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ if (manager.getNotificationChannel(CHANNEL_ID) != null) return
+ val channel = NotificationChannel(
+ CHANNEL_ID,
+ "Alarma musical",
+ NotificationManager.IMPORTANCE_HIGH
+ ).apply {
+ description = "Sonido de alarma musical con pantalla apagada"
+ enableVibration(true)
+ }
+ manager.createNotificationChannel(channel)
+ }
+
+ private fun requestCode(id: String, slot: Int): Int = 67 * id.hashCode() + slot
+ }
+}
diff --git a/docs/alarmas-pantalla-apagada.md b/docs/alarmas-pantalla-apagada.md
new file mode 100644
index 0000000..25a7825
--- /dev/null
+++ b/docs/alarmas-pantalla-apagada.md
@@ -0,0 +1,30 @@
+# Arquitectura de alarmas con pantalla apagada
+
+## Diagnóstico
+
+El flujo anterior hacía que Android recibiese la alarma con `AlarmManager`, pero el sonido real dependía de que se abriese `MainActivity` y de que Flutter llegase a pintar `PantallaAlarmaSonando`. Con pantalla apagada, Doze o restricciones del fabricante, ese arranque de UI puede retrasarse hasta que el usuario enciende la pantalla.
+
+## Decisión
+
+La alarma debe sonar desde Android nativo en cuanto llega `ACTION_FIRE`. Flutter pasa a ser la interfaz de control para detener, posponer y hacer handoff a la radio de la app, pero no el único origen del sonido.
+
+## Flujo recomendado
+
+1. `AlarmScheduler` programa la alarma con `setAlarmClock` y fallback exact/inexact.
+2. `PluriWaveAlarmReceiver` recibe `ACTION_FIRE`.
+3. El receiver arranca `PluriWaveAlarmService` como foreground service.
+4. El servicio toma un `PARTIAL_WAKE_LOCK`, muestra notificación foreground y reproduce audio con `USAGE_ALARM`.
+5. La UI Flutter se abre por full-screen intent si Android lo permite.
+6. Al detener/posponer desde Flutter, se manda comando nativo para parar el servicio.
+
+## Referencias
+
+- Android alarms: https://developer.android.com/develop/background-work/services/alarms
+- Foreground service restrictions: https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start
+- AOSP DeskClock AlarmService: https://android.googlesource.com/platform/packages/apps/DeskClock/+/ac260c0096605526f772af7eec73d6a51dc6de32/src/com/android/deskclock/alarms/AlarmService.java
+
+## Notas
+
+- El audio local interno es el fallback más fiable para pantalla apagada.
+- La radio remota puede fallar por red, DNS, TLS o timeout; por eso debe existir fallback interno.
+- Si un fabricante bloquea incluso servicios arrancados desde alarma, habrá que guiar al usuario con permisos de batería/autostart.
diff --git a/lib/app.dart b/lib/app.dart
index 420fb4f..8d08c94 100644
--- a/lib/app.dart
+++ b/lib/app.dart
@@ -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(
- 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(
- 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(
+ 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,
),
],
diff --git a/lib/servicios/servicio_alarmas_android.dart b/lib/servicios/servicio_alarmas_android.dart
index 5efa275..8bf5cf4 100644
--- a/lib/servicios/servicio_alarmas_android.dart
+++ b/lib/servicios/servicio_alarmas_android.dart
@@ -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 ocultarNotificacionAlarma(String alarmaId) =>
_logAndInvokeVoid('dismissAlarmNotification', {'id': alarmaId});
+ Future detenerSonidoNativo(String alarmaId) =>
+ _logAndInvokeVoid('stopNativeAlarmSound', {'id': alarmaId});
+
Future diagnostico() async {
debugPrint('[PluriWave][alarmas] diagnostico android');
final raw = await _channel.invokeMethod