fix(alarm): localize pre-notice countdown and fix snooze dismiss
Replace hardcoded Spanish pre-notice text with computed remaining minutes using l10n template passed via MethodChannel. Fix snooze dismiss in dead-app state with canPop guard and SystemNavigator.pop fallback.
This commit is contained in:
@@ -39,7 +39,8 @@ class AlarmScheduler(private val context: Context) {
|
||||
snoozeMinutes: Int = 5,
|
||||
fallbackStationName: String? = null,
|
||||
fallbackStationUrl: String? = null,
|
||||
fadeInSegundos: Int = 0
|
||||
fadeInSegundos: Int = 0,
|
||||
preNoticeTemplate: String? = null
|
||||
): Boolean {
|
||||
val existing = readSpec(id)
|
||||
val preservedSnooze = preserveNativeSnooze(
|
||||
@@ -70,7 +71,8 @@ class AlarmScheduler(private val context: Context) {
|
||||
fallbackSound = fallbackSound,
|
||||
volume = volume.coerceIn(0f, 1f),
|
||||
fadeInSegundos = fadeInSegundos.coerceIn(0, 60),
|
||||
timezoneId = TimeZone.getDefault().id
|
||||
timezoneId = TimeZone.getDefault().id,
|
||||
preNoticeTemplate = preNoticeTemplate
|
||||
)
|
||||
return scheduleSpec(spec, persistOnSuccess = true)
|
||||
}
|
||||
@@ -145,6 +147,7 @@ class AlarmScheduler(private val context: Context) {
|
||||
PluriWaveAlarmReceiver.EXTRA_OCCURRENCE_AT,
|
||||
spec.snoozeOriginMillis ?: spec.triggerAtMillis
|
||||
)
|
||||
putExtra(EXTRA_PRE_NOTICE_TEMPLATE, spec.preNoticeTemplate)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
@@ -165,6 +168,7 @@ class AlarmScheduler(private val context: Context) {
|
||||
PluriWaveAlarmReceiver.EXTRA_OCCURRENCE_AT,
|
||||
spec.snoozeOriginMillis ?: spec.triggerAtMillis
|
||||
)
|
||||
putExtra(EXTRA_PRE_NOTICE_TEMPLATE, spec.preNoticeTemplate)
|
||||
}
|
||||
)
|
||||
Log.d(tag, "alarm.schedule preNotice immediate id=${spec.id}")
|
||||
@@ -652,7 +656,10 @@ class AlarmScheduler(private val context: Context) {
|
||||
val fallbackSound: String?,
|
||||
val volume: Float,
|
||||
val fadeInSegundos: Int = 0,
|
||||
val timezoneId: String
|
||||
val timezoneId: String,
|
||||
// Nullable for backward compat: old persisted alarms without this field
|
||||
// fall back to the English default in the receiver. Schema stays v3.
|
||||
val preNoticeTemplate: String? = null
|
||||
) {
|
||||
fun toJson(): JSONObject = JSONObject().apply {
|
||||
put("schemaVersion", 3)
|
||||
@@ -679,6 +686,7 @@ class AlarmScheduler(private val context: Context) {
|
||||
put("volume", volume)
|
||||
put("fadeInSegundos", fadeInSegundos)
|
||||
put("timezoneId", timezoneId)
|
||||
put("preNoticeTemplate", preNoticeTemplate)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -715,7 +723,8 @@ class AlarmScheduler(private val context: Context) {
|
||||
fallbackSound = json.optString("fallbackSound").takeIf { it.isNotBlank() },
|
||||
volume = json.optDouble("volume", 0.85).toFloat(),
|
||||
fadeInSegundos = json.optInt("fadeInSegundos", 0).coerceIn(0, 60),
|
||||
timezoneId = json.optString("timezoneId", TimeZone.getDefault().id)
|
||||
timezoneId = json.optString("timezoneId", TimeZone.getDefault().id),
|
||||
preNoticeTemplate = json.optString("preNoticeTemplate").takeIf { it.isNotBlank() }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -730,6 +739,9 @@ class AlarmScheduler(private val context: Context) {
|
||||
private const val PRE_NOTICE_MILLIS = 30 * 60 * 1000L
|
||||
private const val SCHEDULE_UNICA = "unica"
|
||||
private const val SCHEDULE_DIAS_SEMANA = "diasSemana"
|
||||
// Intent extra key for the localized pre-notice template string.
|
||||
// Declared once here; PluriWaveAlarmReceiver reads it via this constant.
|
||||
const val EXTRA_PRE_NOTICE_TEMPLATE = "preNoticeTemplate"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,7 +115,8 @@ class MainActivity : AudioServiceActivity() {
|
||||
snoozeMinutes = call.argument<Int>("snoozeMinutes") ?: 5,
|
||||
fallbackStationName = call.argument<String>("fallbackStationName"),
|
||||
fallbackStationUrl = call.argument<String>("fallbackStationUrl"),
|
||||
fadeInSegundos = call.argument<Int>("fadeInSegundos") ?: 0
|
||||
fadeInSegundos = call.argument<Int>("fadeInSegundos") ?: 0,
|
||||
preNoticeTemplate = call.argument<String>("preNoticeTemplate")
|
||||
)
|
||||
result.success(scheduled)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
|
||||
title,
|
||||
snoozeMinutes,
|
||||
intent.getLongExtra(EXTRA_TRIGGER_AT, 0L),
|
||||
intent.getLongExtra(EXTRA_OCCURRENCE_AT, 0L)
|
||||
intent.getLongExtra(EXTRA_OCCURRENCE_AT, 0L),
|
||||
intent.getStringExtra(AlarmScheduler.EXTRA_PRE_NOTICE_TEMPLATE)
|
||||
)
|
||||
}
|
||||
ACTION_POSTPONE_NEXT -> {
|
||||
@@ -100,10 +101,14 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
|
||||
title: String,
|
||||
snoozeMinutes: Int,
|
||||
triggerAtMillis: Long,
|
||||
occurrenceAtMillis: Long
|
||||
occurrenceAtMillis: Long,
|
||||
preNoticeTemplate: String? = null
|
||||
) {
|
||||
ensureChannel(context)
|
||||
|
||||
val remaining = computeRemainingMinutes(triggerAtMillis)
|
||||
val contentText = formatPreNoticeText(preNoticeTemplate, remaining)
|
||||
|
||||
val openAppIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
requestCode(alarmId, 1),
|
||||
@@ -144,7 +149,7 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
|
||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
||||
.setContentTitle(title)
|
||||
.setContentText("Empieza en 30 minutos")
|
||||
.setContentText(contentText)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setCategory(NotificationCompat.CATEGORY_REMINDER)
|
||||
.setSilent(true)
|
||||
@@ -156,12 +161,31 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
|
||||
|
||||
try {
|
||||
NotificationManagerCompat.from(context).notify(notificationIdForAlarm(alarmId), notification)
|
||||
Log.d(TAG, "alarm.notification preNotice shown id=$alarmId")
|
||||
Log.d(TAG, "alarm.notification preNotice shown id=$alarmId remaining=$remaining")
|
||||
} catch (error: SecurityException) {
|
||||
Log.e(TAG, "alarm.notification preNotice SecurityException id=$alarmId", error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the number of minutes remaining until [triggerAtMillis],
|
||||
* clamped to a minimum of 1. Handles Doze-delayed wakeups and clock drift.
|
||||
*/
|
||||
private fun computeRemainingMinutes(triggerAtMillis: Long): Long =
|
||||
maxOf(1L, (triggerAtMillis - System.currentTimeMillis()) / 60_000L)
|
||||
|
||||
/**
|
||||
* Formats the pre-notice notification text by replacing the `{minutes}`
|
||||
* placeholder in [template] with [remaining]. Falls back to an English
|
||||
* default if [template] is null or blank.
|
||||
*/
|
||||
private fun formatPreNoticeText(template: String?, remaining: Long): String {
|
||||
if (template.isNullOrBlank()) {
|
||||
return "Starts in $remaining min"
|
||||
}
|
||||
return template.replace("{minutes}", remaining.toString())
|
||||
}
|
||||
|
||||
private fun ensureChannel(context: Context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
Reference in New Issue
Block a user