fix(recordings): open last file on android
Build & Deploy Pluriwave / Análisis de código (push) Successful in 23s
Build & Deploy Pluriwave / Build APK + AAB release (push) Failing after 1m2s

This commit is contained in:
2026-05-22 18:30:38 +02:00
parent fde651eee9
commit 809255bd43
8 changed files with 184 additions and 15 deletions
+10
View File
@@ -68,6 +68,16 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/pluriwave_file_paths" />
</provider>
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
@@ -5,13 +5,19 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.util.Log
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
class AlarmScheduler(private val context: Context) { class AlarmScheduler(private val context: Context) {
private val tag = "PluriWave"
private val alarmManager = private val alarmManager =
context.getSystemService(Context.ALARM_SERVICE) as AlarmManager context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
fun scheduleAlarm(id: String, title: String, triggerAtMillis: Long, preNoticeAtMillis: Long) { fun scheduleAlarm(id: String, title: String, triggerAtMillis: Long, preNoticeAtMillis: Long) {
Log.d(
tag,
"alarm.schedule id=$id title=$title triggerAtMillis=$triggerAtMillis preNoticeAtMillis=$preNoticeAtMillis canExact=${canScheduleExactAlarms()}"
)
val alarmIntent = PendingIntent.getBroadcast( val alarmIntent = PendingIntent.getBroadcast(
context, context,
requestCode(id, 1), requestCode(id, 1),
@@ -39,6 +45,7 @@ class AlarmScheduler(private val context: Context) {
AlarmManager.AlarmClockInfo(triggerAtMillis, showIntent), AlarmManager.AlarmClockInfo(triggerAtMillis, showIntent),
alarmIntent alarmIntent
) )
Log.d(tag, "alarm.schedule setAlarmClock OK id=$id")
if (preNoticeAtMillis > System.currentTimeMillis()) { if (preNoticeAtMillis > System.currentTimeMillis()) {
try { try {
@@ -56,13 +63,18 @@ class AlarmScheduler(private val context: Context) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
) )
Log.d(tag, "alarm.schedule preNotice OK id=$id")
} catch (_: SecurityException) { } catch (_: SecurityException) {
// The main alarm is already scheduled with setAlarmClock. // The main alarm is already scheduled with setAlarmClock.
Log.w(tag, "alarm.schedule preNotice SecurityException id=$id")
} }
} else {
Log.d(tag, "alarm.schedule preNotice skipped id=$id")
} }
} }
fun cancelAlarm(id: String) { fun cancelAlarm(id: String) {
Log.d(tag, "alarm.cancel id=$id")
for (slot in 1..3) { for (slot in 1..3) {
alarmManager.cancel( alarmManager.cancel(
PendingIntent.getBroadcast( PendingIntent.getBroadcast(
@@ -2,6 +2,7 @@ package es.freetimelab.pluriwave
import android.Manifest import android.Manifest
import android.app.ActivityNotFoundException import android.app.ActivityNotFoundException
import android.content.ClipData
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
@@ -11,13 +12,17 @@ import android.os.Environment
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.util.Log
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.FileProvider
import com.ryanheise.audioservice.AudioServiceActivity import com.ryanheise.audioservice.AudioServiceActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import java.io.File
class MainActivity : AudioServiceActivity() { class MainActivity : AudioServiceActivity() {
private val tag = "PluriWave"
private val visualizerChannel = "pluriwave/audio_visualizer" private val visualizerChannel = "pluriwave/audio_visualizer"
private val alarmChannel = "pluriwave/alarm_scheduler" private val alarmChannel = "pluriwave/alarm_scheduler"
private val fileActionsChannel = "pluriwave/file_actions" private val fileActionsChannel = "pluriwave/file_actions"
@@ -59,7 +64,9 @@ class MainActivity : AudioServiceActivity() {
val title = call.argument<String>("title") ?: "PluriWave" val title = call.argument<String>("title") ?: "PluriWave"
val triggerAtMillis = call.argument<Long>("triggerAtMillis") val triggerAtMillis = call.argument<Long>("triggerAtMillis")
val preNoticeAtMillis = call.argument<Long>("preNoticeAtMillis") ?: 0L val preNoticeAtMillis = call.argument<Long>("preNoticeAtMillis") ?: 0L
Log.d(tag, "alarm.channel scheduleAlarm id=$id triggerAtMillis=$triggerAtMillis preNoticeAtMillis=$preNoticeAtMillis")
if (id == null || triggerAtMillis == null) { 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) result.error("INVALID_ALARM", "Missing alarm id or trigger time", null)
} else { } else {
alarmScheduler.scheduleAlarm(id, title, triggerAtMillis, preNoticeAtMillis) alarmScheduler.scheduleAlarm(id, title, triggerAtMillis, preNoticeAtMillis)
@@ -68,6 +75,7 @@ class MainActivity : AudioServiceActivity() {
} }
"cancelAlarm" -> { "cancelAlarm" -> {
val id = call.argument<String>("id") val id = call.argument<String>("id")
Log.d(tag, "alarm.channel cancelAlarm id=$id")
if (id == null) { if (id == null) {
result.error("INVALID_ALARM", "Missing alarm id", null) result.error("INVALID_ALARM", "Missing alarm id", null)
} else { } else {
@@ -77,6 +85,7 @@ class MainActivity : AudioServiceActivity() {
} }
"dismissAlarmNotification" -> { "dismissAlarmNotification" -> {
val id = call.argument<String>("id") val id = call.argument<String>("id")
Log.d(tag, "alarm.channel dismissAlarmNotification id=$id")
if (id == null) { if (id == null) {
result.error("INVALID_ALARM", "Missing alarm id", null) result.error("INVALID_ALARM", "Missing alarm id", null)
} else { } else {
@@ -85,6 +94,7 @@ class MainActivity : AudioServiceActivity() {
} }
} }
"diagnostics" -> { "diagnostics" -> {
Log.d(tag, "alarm.channel diagnostics")
result.success( result.success(
mapOf( mapOf(
"canScheduleExactAlarms" to alarmScheduler.canScheduleExactAlarms(), "canScheduleExactAlarms" to alarmScheduler.canScheduleExactAlarms(),
@@ -95,7 +105,9 @@ class MainActivity : AudioServiceActivity() {
) )
} }
"getInitialAlarmIntent" -> { "getInitialAlarmIntent" -> {
result.success(alarmPayload(intent)) val payload = alarmPayload(intent)
Log.d(tag, "alarm.channel getInitialAlarmIntent payload=$payload")
result.success(payload)
intent?.removeExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ACTION) intent?.removeExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ACTION)
} }
else -> result.notImplemented() else -> result.notImplemented()
@@ -109,12 +121,23 @@ class MainActivity : AudioServiceActivity() {
when (call.method) { when (call.method) {
"openDirectory" -> { "openDirectory" -> {
val path = call.argument<String>("path") val path = call.argument<String>("path")
Log.d(tag, "file_actions.openDirectory path=$path")
if (path.isNullOrBlank()) { if (path.isNullOrBlank()) {
result.success(false) result.success(false)
} else { } else {
result.success(openDirectory(path)) result.success(openDirectory(path))
} }
} }
"openFile" -> {
val path = call.argument<String>("path")
val mimeType = call.argument<String>("mimeType") ?: "audio/*"
Log.d(tag, "file_actions.openFile path=$path mimeType=$mimeType")
if (path.isNullOrBlank()) {
result.success(false)
} else {
result.success(openFile(path, mimeType))
}
}
else -> result.notImplemented() else -> result.notImplemented()
} }
} }
@@ -125,6 +148,7 @@ class MainActivity : AudioServiceActivity() {
setIntent(intent) setIntent(intent)
val payload = alarmPayload(intent) val payload = alarmPayload(intent)
if (payload.isNotEmpty()) { if (payload.isNotEmpty()) {
Log.d(tag, "alarm.channel onNewIntent payload=$payload")
alarmMethodChannel?.invokeMethod("alarmFired", payload) alarmMethodChannel?.invokeMethod("alarmFired", payload)
} }
} }
@@ -156,14 +180,46 @@ class MainActivity : AudioServiceActivity() {
} }
return try { return try {
startActivity(intent) startActivity(intent)
Log.d(tag, "file_actions.openDirectory launched path=$path")
true true
} catch (_: ActivityNotFoundException) { } catch (_: ActivityNotFoundException) {
Log.w(tag, "file_actions.openDirectory no activity for path=$path")
false false
} catch (_: Throwable) { } catch (error: Throwable) {
Log.e(tag, "file_actions.openDirectory failed path=$path", error)
false false
} }
} }
private fun openFile(path: String, mimeType: String): Boolean {
val file = File(path)
if (!file.exists()) {
Log.w(tag, "file_actions.openFile missing path=$path")
return false
}
return try {
val uri = FileProvider.getUriForFile(
this,
"$packageName.fileprovider",
file
)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, mimeType)
clipData = ClipData.newUri(contentResolver, "recording", uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, "Abrir grabación"))
Log.d(tag, "file_actions.openFile launched path=$path")
true
} catch (_: ActivityNotFoundException) {
Log.w(tag, "file_actions.openFile no viewer path=$path; opening parent")
openDirectory(file.parentFile?.absolutePath ?: path)
} catch (error: Throwable) {
Log.e(tag, "file_actions.openFile failed path=$path; opening parent", error)
openDirectory(file.parentFile?.absolutePath ?: path)
}
}
private fun directoryTreeUri(path: String): Uri? { private fun directoryTreeUri(path: String): Uri? {
val external = Environment.getExternalStorageDirectory()?.absolutePath ?: return null val external = Environment.getExternalStorageDirectory()?.absolutePath ?: return null
if (!path.startsWith(external)) return null if (!path.startsWith(external)) return null
@@ -7,13 +7,18 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
class PluriWaveAlarmReceiver : BroadcastReceiver() { class PluriWaveAlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
val alarmId = intent.getStringExtra(EXTRA_ALARM_ID) ?: return val alarmId = intent.getStringExtra(EXTRA_ALARM_ID) ?: run {
Log.w(TAG, "alarm.receiver missing alarmId action=${intent.action}")
return
}
val title = intent.getStringExtra(EXTRA_ALARM_TITLE) ?: "PluriWave" val title = intent.getStringExtra(EXTRA_ALARM_TITLE) ?: "PluriWave"
Log.d(TAG, "alarm.receiver action=${intent.action} id=$alarmId title=$title")
when (intent.action) { when (intent.action) {
ACTION_FIRE -> { ACTION_FIRE -> {
@@ -24,7 +29,12 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
putExtra(EXTRA_ALARM_ACTION, ACTION_FIRE) putExtra(EXTRA_ALARM_ACTION, ACTION_FIRE)
} }
showFireNotification(context, alarmId, title, launch) showFireNotification(context, alarmId, title, launch)
context.startActivity(launch) try {
context.startActivity(launch)
Log.d(TAG, "alarm.receiver fire startActivity OK id=$alarmId")
} catch (error: Throwable) {
Log.e(TAG, "alarm.receiver fire startActivity ERROR id=$alarmId", error)
}
} }
ACTION_PRE_NOTICE -> { ACTION_PRE_NOTICE -> {
showPreNoticeNotification(context, alarmId, title) showPreNoticeNotification(context, alarmId, title)
@@ -37,8 +47,14 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
putExtra(EXTRA_ALARM_TITLE, title) putExtra(EXTRA_ALARM_TITLE, title)
putExtra(EXTRA_ALARM_ACTION, ACTION_SKIP_NEXT) putExtra(EXTRA_ALARM_ACTION, ACTION_SKIP_NEXT)
} }
context.startActivity(launch) try {
context.startActivity(launch)
Log.d(TAG, "alarm.receiver skipNext startActivity OK id=$alarmId")
} catch (error: Throwable) {
Log.e(TAG, "alarm.receiver skipNext startActivity ERROR id=$alarmId", error)
}
} }
else -> Log.w(TAG, "alarm.receiver unknown action=${intent.action} id=$alarmId")
} }
} }
@@ -72,7 +88,9 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
fireNotificationIdForAlarm(alarmId), fireNotificationIdForAlarm(alarmId),
notification, notification,
) )
} catch (_: SecurityException) { Log.d(TAG, "alarm.notification fire shown id=$alarmId")
} catch (error: SecurityException) {
Log.e(TAG, "alarm.notification fire SecurityException id=$alarmId", error)
} }
} }
@@ -114,7 +132,12 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
.addAction(0, "Omitir siguiente", skipNextIntent) .addAction(0, "Omitir siguiente", skipNextIntent)
.build() .build()
NotificationManagerCompat.from(context).notify(notificationIdForAlarm(alarmId), notification) try {
NotificationManagerCompat.from(context).notify(notificationIdForAlarm(alarmId), notification)
Log.d(TAG, "alarm.notification preNotice shown id=$alarmId")
} catch (error: SecurityException) {
Log.e(TAG, "alarm.notification preNotice SecurityException id=$alarmId", error)
}
} }
private fun ensureFireChannel(context: Context) { private fun ensureFireChannel(context: Context) {
@@ -155,6 +178,7 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
private fun requestCode(id: String, slot: Int): Int = 47 * id.hashCode() + slot private fun requestCode(id: String, slot: Int): Int = 47 * id.hashCode() + slot
companion object { companion object {
const val TAG = "PluriWave"
const val CHANNEL_ID = "pluriwave_alarm_pre_notice" const val CHANNEL_ID = "pluriwave_alarm_pre_notice"
const val FIRE_CHANNEL_ID = "pluriwave_alarm_fire" const val FIRE_CHANNEL_ID = "pluriwave_alarm_fire"
const val ACTION_FIRE = "es.freetimelab.pluriwave.alarm.FIRE" const val ACTION_FIRE = "es.freetimelab.pluriwave.alarm.FIRE"
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path
name="files"
path="." />
<cache-path
name="cache"
path="." />
<external-files-path
name="external_files"
path="." />
<external-cache-path
name="external_cache"
path="." />
</paths>
+17 -2
View File
@@ -49,17 +49,20 @@ class EstadoAlarmas extends ChangeNotifier {
} }
Future<void> inicializar() async { Future<void> inicializar() async {
debugPrint('[PluriWave][alarmas] inicializar');
_cargando = true; _cargando = true;
_error = null; _error = null;
notifyListeners(); notifyListeners();
try { try {
final config = await servicio.cargar(); final config = await servicio.cargar();
_aplicar(config); _aplicar(config);
debugPrint('[PluriWave][alarmas] cargadas=${_alarmas.length} vacaciones=${_vacaciones.length} excepciones=${_excepciones.length}');
await _sincronizarTodas(); await _sincronizarTodas();
await cargarDiagnostico(); await cargarDiagnostico();
_activarRefresco(); _activarRefresco();
} catch (e) { } catch (e) {
_error = 'No se pudieron cargar las alarmas: $e'; _error = 'No se pudieron cargar las alarmas: $e';
debugPrint('[PluriWave][alarmas] inicializar ERROR $e');
} finally { } finally {
_cargando = false; _cargando = false;
notifyListeners(); notifyListeners();
@@ -67,10 +70,13 @@ class EstadoAlarmas extends ChangeNotifier {
} }
Future<void> guardarAlarma(AlarmaMusical alarma) async { Future<void> guardarAlarma(AlarmaMusical alarma) async {
debugPrint('[PluriWave][alarmas] guardar id=${alarma.id} activa=${alarma.activa} hora=${alarma.hora}:${alarma.minuto} tipo=${alarma.tipoProgramacion.name}');
final config = await servicio.guardarAlarma(alarma); final config = await servicio.guardarAlarma(alarma);
_aplicar(config); _aplicar(config);
try { try {
await android.programar(_alarmas.firstWhere((a) => a.id == alarma.id)); final guardada = _alarmas.firstWhere((a) => a.id == alarma.id);
debugPrint('[PluriWave][alarmas] guardada id=${guardada.id} proxima=${guardada.proximaEjecucion?.toIso8601String()}');
await android.programar(guardada);
} catch (e) { } catch (e) {
_error = _error =
'Alarma guardada, pero Android no pudo programarla todavía: $e'; 'Alarma guardada, pero Android no pudo programarla todavía: $e';
@@ -79,6 +85,7 @@ class EstadoAlarmas extends ChangeNotifier {
} }
Future<void> refrescarProgramacion() async { Future<void> refrescarProgramacion() async {
debugPrint('[PluriWave][alarmas] refrescar programacion');
final config = await servicio.recalcularTodas(); final config = await servicio.recalcularTodas();
_aplicar(config); _aplicar(config);
await _sincronizarTodas(); await _sincronizarTodas();
@@ -86,6 +93,7 @@ class EstadoAlarmas extends ChangeNotifier {
} }
Future<void> eliminarAlarma(String id) async { Future<void> eliminarAlarma(String id) async {
debugPrint('[PluriWave][alarmas] eliminar id=$id');
final config = await servicio.eliminarAlarma(id); final config = await servicio.eliminarAlarma(id);
_aplicar(config); _aplicar(config);
await android.cancelar(id); await android.cancelar(id);
@@ -97,6 +105,7 @@ class EstadoAlarmas extends ChangeNotifier {
} }
Future<void> saltarProxima(String alarmaId) async { Future<void> saltarProxima(String alarmaId) async {
debugPrint('[PluriWave][alarmas] saltar proxima id=$alarmaId');
final config = await servicio.saltarProxima(alarmaId); final config = await servicio.saltarProxima(alarmaId);
_aplicar(config); _aplicar(config);
AlarmaMusical? alarma; AlarmaMusical? alarma;
@@ -113,6 +122,7 @@ class EstadoAlarmas extends ChangeNotifier {
} }
Future<void> guardarVacaciones(List<RangoVacaciones> vacaciones) async { Future<void> guardarVacaciones(List<RangoVacaciones> vacaciones) async {
debugPrint('[PluriWave][alarmas] guardar vacaciones count=${vacaciones.length}');
final config = await servicio.guardarVacaciones(vacaciones); final config = await servicio.guardarVacaciones(vacaciones);
_aplicar(config); _aplicar(config);
await _sincronizarTodas(); await _sincronizarTodas();
@@ -121,11 +131,13 @@ class EstadoAlarmas extends ChangeNotifier {
Future<void> posponerAlarma(AlarmaMusical alarma, int minutos) async { Future<void> posponerAlarma(AlarmaMusical alarma, int minutos) async {
final proxima = DateTime.now().add(Duration(minutes: minutos)); final proxima = DateTime.now().add(Duration(minutes: minutos));
debugPrint('[PluriWave][alarmas] posponer id=${alarma.id} minutos=$minutos proxima=${proxima.toIso8601String()}');
await android.ocultarNotificacionAlarma(alarma.id); await android.ocultarNotificacionAlarma(alarma.id);
await android.programar(alarma.copyWith(proximaEjecucion: proxima)); await android.programar(alarma.copyWith(proximaEjecucion: proxima));
} }
Future<void> finalizarEjecucion(String alarmaId) async { Future<void> finalizarEjecucion(String alarmaId) async {
debugPrint('[PluriWave][alarmas] finalizar ejecucion id=$alarmaId');
await android.ocultarNotificacionAlarma(alarmaId); await android.ocultarNotificacionAlarma(alarmaId);
await refrescarProgramacion(); await refrescarProgramacion();
} }
@@ -150,13 +162,15 @@ class EstadoAlarmas extends ChangeNotifier {
Future<void> cargarDiagnostico() async { Future<void> cargarDiagnostico() async {
try { try {
_diagnostico = await android.diagnostico(); _diagnostico = await android.diagnostico();
} catch (_) { } catch (e) {
debugPrint('[PluriWave][alarmas] diagnostico ERROR $e');
_diagnostico = null; _diagnostico = null;
} }
notifyListeners(); notifyListeners();
} }
Future<void> _sincronizarTodas() async { Future<void> _sincronizarTodas() async {
debugPrint('[PluriWave][alarmas] sincronizar todas count=${_alarmas.length}');
for (final alarma in _alarmas) { for (final alarma in _alarmas) {
await android.programar(alarma); await android.programar(alarma);
} }
@@ -188,6 +202,7 @@ class EstadoAlarmas extends ChangeNotifier {
if (proxima.isAfter(ahora)) continue; if (proxima.isAfter(ahora)) continue;
final key = '${alarma.id}:${proxima.millisecondsSinceEpoch}'; final key = '${alarma.id}:${proxima.millisecondsSinceEpoch}';
if (_ejecucionesEmitidas.add(key)) { if (_ejecucionesEmitidas.add(key)) {
debugPrint('[PluriWave][alarmas] vencida local id=${alarma.id} proxima=${proxima.toIso8601String()}');
_alarmasVencidasController.add(alarma); _alarmasVencidasController.add(alarma);
} }
} }
+16 -2
View File
@@ -464,7 +464,7 @@ class EstadoRadio extends ChangeNotifier {
); );
} }
_resultadosBusqueda = nuevaLista; _resultadosBusqueda = nuevaLista;
// _buscarPaginaFiltrada actualiza offset/hayMas usando p?ginas crudas. // _buscarPaginaFiltrada actualiza offset/hayMas usando páginas crudas.
_hayMasBusqueda = _hayMasBusqueda && pagina.isNotEmpty; _hayMasBusqueda = _hayMasBusqueda && pagina.isNotEmpty;
} catch (_) { } catch (_) {
_errorController.add('No se pudieron cargar mas emisoras.'); _errorController.add('No se pudieron cargar mas emisoras.');
@@ -644,7 +644,21 @@ class EstadoRadio extends ChangeNotifier {
Future<bool> abrirUltimaGrabacion() async { Future<bool> abrirUltimaGrabacion() async {
final archivo = ultimaGrabacion; final archivo = ultimaGrabacion;
if (archivo == null || !await archivo.exists()) return false; if (archivo == null || !await archivo.exists()) {
debugPrint('[PluriWave][recordings] last recording missing');
return false;
}
debugPrint('[PluriWave][recordings] opening last file: ${archivo.path}');
if (!kIsWeb && Platform.isAndroid) {
final abierto = await _fileActionsChannel.invokeMethod<bool>(
'openFile',
{
'path': archivo.path,
'mimeType': 'audio/*',
},
);
return abierto ?? false;
}
return launchUrl( return launchUrl(
Uri.file(archivo.path), Uri.file(archivo.path),
mode: LaunchMode.externalApplication, mode: LaunchMode.externalApplication,
+27 -4
View File
@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import '../modelos/alarma_musical.dart'; import '../modelos/alarma_musical.dart';
@@ -64,9 +65,15 @@ class ServicioAlarmasAndroid {
Future<void> programar(AlarmaMusical alarma) async { Future<void> programar(AlarmaMusical alarma) async {
final proxima = alarma.proximaEjecucion; final proxima = alarma.proximaEjecucion;
if (proxima == null || !alarma.activa) { if (proxima == null || !alarma.activa) {
debugPrint(
'[PluriWave][alarmas] cancelar por inactiva/sin proxima id=${alarma.id} activa=${alarma.activa} proxima=$proxima',
);
await cancelar(alarma.id); await cancelar(alarma.id);
return; return;
} }
debugPrint(
'[PluriWave][alarmas] programar id=${alarma.id} nombre=${alarma.nombre} proxima=${proxima.toIso8601String()} preaviso=${proxima.subtract(const Duration(minutes: 30)).toIso8601String()}',
);
await _channel.invokeMethod<void>('scheduleAlarm', { await _channel.invokeMethod<void>('scheduleAlarm', {
'id': alarma.id, 'id': alarma.id,
'title': alarma.nombre, 'title': alarma.nombre,
@@ -77,16 +84,21 @@ class ServicioAlarmasAndroid {
} }
Future<void> cancelar(String alarmaId) => Future<void> cancelar(String alarmaId) =>
_channel.invokeMethod<void>('cancelAlarm', {'id': alarmaId}); _logAndInvokeVoid('cancelAlarm', {'id': alarmaId});
Future<void> ocultarNotificacionAlarma(String alarmaId) => _channel Future<void> ocultarNotificacionAlarma(String alarmaId) =>
.invokeMethod<void>('dismissAlarmNotification', {'id': alarmaId}); _logAndInvokeVoid('dismissAlarmNotification', {'id': alarmaId});
Future<DiagnosticoAlarmasAndroid> diagnostico() async { Future<DiagnosticoAlarmasAndroid> diagnostico() async {
debugPrint('[PluriWave][alarmas] diagnostico android');
final raw = await _channel.invokeMethod<Map<Object?, Object?>>( final raw = await _channel.invokeMethod<Map<Object?, Object?>>(
'diagnostics', 'diagnostics',
); );
return DiagnosticoAlarmasAndroid.fromMap(raw ?? const {}); final diag = DiagnosticoAlarmasAndroid.fromMap(raw ?? const {});
debugPrint(
'[PluriWave][alarmas] diagnostico exactas=${diag.puedeProgramarExactas} notificaciones=${diag.notificacionesPermitidas} sdk=${diag.versionSdk} fabricante=${diag.fabricante}',
);
return diag;
} }
Future<EventoAlarmaAndroid?> obtenerEventoInicial() async { Future<EventoAlarmaAndroid?> obtenerEventoInicial() async {
@@ -95,9 +107,17 @@ class ServicioAlarmasAndroid {
); );
if (raw == null || raw.isEmpty) return null; if (raw == null || raw.isEmpty) return null;
final evento = EventoAlarmaAndroid.fromMap(raw); final evento = EventoAlarmaAndroid.fromMap(raw);
debugPrint(
'[PluriWave][alarmas] evento inicial id=${evento.alarmaId} accion=${evento.accion}',
);
return evento.alarmaId.isEmpty ? null : evento; return evento.alarmaId.isEmpty ? null : evento;
} }
Future<void> _logAndInvokeVoid(String method, Map<String, Object?> args) {
debugPrint('[PluriWave][alarmas] $method $args');
return _channel.invokeMethod<void>(method, args);
}
static void _instalarHandler(MethodChannel channel) { static void _instalarHandler(MethodChannel channel) {
if (_handlerInstalado) return; if (_handlerInstalado) return;
_handlerInstalado = true; _handlerInstalado = true;
@@ -107,6 +127,9 @@ class ServicioAlarmasAndroid {
if (args is Map) { if (args is Map) {
final evento = EventoAlarmaAndroid.fromMap(args); final evento = EventoAlarmaAndroid.fromMap(args);
if (evento.alarmaId.isNotEmpty) { if (evento.alarmaId.isNotEmpty) {
debugPrint(
'[PluriWave][alarmas] evento nativo id=${evento.alarmaId} accion=${evento.accion}',
);
_eventosController.add(evento); _eventosController.add(evento);
} }
} }