feat(alarm): complete musical alarm flows
This commit is contained in:
@@ -5,6 +5,7 @@ import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
|
||||
class AlarmScheduler(private val context: Context) {
|
||||
private val alarmManager =
|
||||
@@ -28,6 +29,8 @@ class AlarmScheduler(private val context: Context) {
|
||||
Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ID, id)
|
||||
putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_TITLE, title)
|
||||
putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ACTION, PluriWaveAlarmReceiver.ACTION_FIRE)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
@@ -66,6 +69,9 @@ class AlarmScheduler(private val context: Context) {
|
||||
) ?: continue
|
||||
)
|
||||
}
|
||||
NotificationManagerCompat.from(context).cancel(
|
||||
PluriWaveAlarmReceiver.notificationIdForAlarm(id)
|
||||
)
|
||||
}
|
||||
|
||||
fun canScheduleExactAlarms(): Boolean {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package es.freetimelab.pluriwave
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.media.audiofx.Visualizer
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.ryanheise.audioservice.AudioServiceActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
@@ -18,6 +20,7 @@ class MainActivity : AudioServiceActivity() {
|
||||
private var visualizer: Visualizer? = null
|
||||
private var pendingSink: EventChannel.EventSink? = null
|
||||
private var pendingArgs: Map<*, *>? = null
|
||||
private var alarmMethodChannel: MethodChannel? = null
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
@@ -40,10 +43,11 @@ class MainActivity : AudioServiceActivity() {
|
||||
})
|
||||
|
||||
val alarmScheduler = AlarmScheduler(this)
|
||||
MethodChannel(
|
||||
alarmMethodChannel = MethodChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger,
|
||||
alarmChannel
|
||||
).setMethodCallHandler { call, result ->
|
||||
)
|
||||
alarmMethodChannel?.setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"scheduleAlarm" -> {
|
||||
val id = call.argument<String>("id")
|
||||
@@ -70,17 +74,45 @@ class MainActivity : AudioServiceActivity() {
|
||||
result.success(
|
||||
mapOf(
|
||||
"canScheduleExactAlarms" to alarmScheduler.canScheduleExactAlarms(),
|
||||
"notificationsEnabled" to true,
|
||||
"notificationsEnabled" to NotificationManagerCompat.from(this).areNotificationsEnabled(),
|
||||
"manufacturer" to Build.MANUFACTURER,
|
||||
"sdkInt" to Build.VERSION.SDK_INT
|
||||
)
|
||||
)
|
||||
}
|
||||
"getInitialAlarmIntent" -> {
|
||||
result.success(alarmPayload(intent))
|
||||
intent?.removeExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ACTION)
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
val payload = alarmPayload(intent)
|
||||
if (payload.isNotEmpty()) {
|
||||
alarmMethodChannel?.invokeMethod("alarmFired", payload)
|
||||
}
|
||||
}
|
||||
|
||||
private fun alarmPayload(intent: Intent?): Map<String, Any> {
|
||||
if (intent == null) return emptyMap()
|
||||
val action = intent.getStringExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ACTION)
|
||||
?: return emptyMap()
|
||||
val alarmId = intent.getStringExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ID)
|
||||
?: return emptyMap()
|
||||
val title = intent.getStringExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_TITLE)
|
||||
?: "PluriWave"
|
||||
return mapOf(
|
||||
"alarmId" to alarmId,
|
||||
"alarmTitle" to title,
|
||||
"alarmAction" to action
|
||||
)
|
||||
}
|
||||
|
||||
private fun startVisualizerWhenAllowed() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||
checkSelfPermission(Manifest.permission.RECORD_AUDIO)
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package es.freetimelab.pluriwave
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
|
||||
class PluriWaveAlarmReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
@@ -20,21 +26,91 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
|
||||
context.startActivity(launch)
|
||||
}
|
||||
ACTION_PRE_NOTICE -> {
|
||||
// MVP: native delivery exists; Flutter will own skip-next UX.
|
||||
// Next batch: notification channel + action button.
|
||||
showPreNoticeNotification(context, alarmId, title)
|
||||
}
|
||||
ACTION_SKIP_NEXT -> {
|
||||
// Next batch: forward skip-next to Flutter persistence or native store.
|
||||
NotificationManagerCompat.from(context).cancel(notificationIdForAlarm(alarmId))
|
||||
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)
|
||||
putExtra(EXTRA_ALARM_TITLE, title)
|
||||
putExtra(EXTRA_ALARM_ACTION, ACTION_SKIP_NEXT)
|
||||
}
|
||||
context.startActivity(launch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showPreNoticeNotification(context: Context, alarmId: String, title: String) {
|
||||
ensureChannel(context)
|
||||
|
||||
val openAppIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
requestCode(alarmId, 1),
|
||||
Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra(EXTRA_ALARM_ID, alarmId)
|
||||
putExtra(EXTRA_ALARM_TITLE, title)
|
||||
putExtra(EXTRA_ALARM_ACTION, ACTION_PRE_NOTICE)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val skipNextIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
requestCode(alarmId, 2),
|
||||
Intent(context, PluriWaveAlarmReceiver::class.java).apply {
|
||||
action = ACTION_SKIP_NEXT
|
||||
putExtra(EXTRA_ALARM_ID, alarmId)
|
||||
putExtra(EXTRA_ALARM_TITLE, title)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
||||
.setContentTitle(title)
|
||||
.setContentText("Empieza en 30 minutos")
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setCategory(NotificationCompat.CATEGORY_REMINDER)
|
||||
.setSilent(true)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(openAppIntent)
|
||||
.addAction(0, "Omitir siguiente", skipNextIntent)
|
||||
.build()
|
||||
|
||||
NotificationManagerCompat.from(context).notify(notificationIdForAlarm(alarmId), notification)
|
||||
}
|
||||
|
||||
private fun ensureChannel(context: Context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val existing = manager.getNotificationChannel(CHANNEL_ID)
|
||||
if (existing != null) return
|
||||
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
"Preavisos de alarmas",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
description = "Notificaciones silenciosas 30 minutos antes de la alarma"
|
||||
setSound(null, null)
|
||||
enableVibration(false)
|
||||
}
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
private fun requestCode(id: String, slot: Int): Int = 47 * id.hashCode() + slot
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID = "pluriwave_alarm_pre_notice"
|
||||
const val ACTION_FIRE = "es.freetimelab.pluriwave.alarm.FIRE"
|
||||
const val ACTION_PRE_NOTICE = "es.freetimelab.pluriwave.alarm.PRE_NOTICE"
|
||||
const val ACTION_SKIP_NEXT = "es.freetimelab.pluriwave.alarm.SKIP_NEXT"
|
||||
const val EXTRA_ALARM_ID = "alarmId"
|
||||
const val EXTRA_ALARM_TITLE = "alarmTitle"
|
||||
const val EXTRA_ALARM_ACTION = "alarmAction"
|
||||
|
||||
fun notificationIdForAlarm(alarmId: String): Int = 53 * alarmId.hashCode() + 7
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user