Merge pull request #147 from FossifyOrg/fix_onetime_alarms

Fix issue with re-enabling alarms for current day
This commit is contained in:
Naveen Singh
2025-04-13 14:44:08 +05:30
committed by GitHub
10 changed files with 87 additions and 97 deletions

View File

@@ -188,7 +188,7 @@ class AlarmActivity : SimpleActivity() {
} else if (config.useSameSnooze) {
dismissAlarmAndFinish(config.snoozeTime)
} else {
alarmController.stopAlarm(alarmId = alarm!!.id, disable = false)
alarmController.silenceAlarm()
showPickSecondsDialog(
curSeconds = config.snoozeTime * MINUTE_SECONDS,
isSnoozePicker = true,

View File

@@ -11,8 +11,9 @@ import org.fossify.commons.helpers.MINUTE_SECONDS
class SnoozeReminderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
alarmController.silenceAlarm()
val alarmId = intent.getIntExtra(ALARM_ID, -1)
alarmController.stopAlarm(alarmId = alarmId, disable = false)
showPickSecondsDialog(
curSeconds = config.snoozeTime * MINUTE_SECONDS,
isSnoozePicker = true,

View File

@@ -12,11 +12,8 @@ import org.fossify.clock.activities.SimpleActivity
import org.fossify.clock.databinding.ItemAlarmBinding
import org.fossify.clock.extensions.config
import org.fossify.clock.extensions.dbHelper
import org.fossify.clock.extensions.getAlarmSelectedDaysString
import org.fossify.clock.extensions.getFormattedTime
import org.fossify.clock.extensions.swap
import org.fossify.clock.helpers.TOMORROW_BIT
import org.fossify.clock.helpers.getCurrentDayMinutes
import org.fossify.clock.helpers.updateNonRecurringAlarmDay
import org.fossify.clock.interfaces.ToggleAlarmInterface
import org.fossify.clock.models.Alarm
import org.fossify.clock.models.AlarmEvent
@@ -24,7 +21,9 @@ import org.fossify.commons.adapters.MyRecyclerViewAdapter
import org.fossify.commons.dialogs.ConfirmationDialog
import org.fossify.commons.extensions.applyColorFilter
import org.fossify.commons.extensions.beVisibleIf
import org.fossify.commons.extensions.toast
import org.fossify.commons.extensions.getSelectedDaysString
import org.fossify.commons.extensions.move
import org.fossify.commons.helpers.EVERY_DAY_BIT
import org.fossify.commons.helpers.SORT_BY_CUSTOM
import org.fossify.commons.interfaces.ItemMoveCallback
import org.fossify.commons.interfaces.ItemTouchHelperContract
@@ -152,7 +151,7 @@ class AlarmsAdapter(
)
alarmTime.setTextColor(textColor)
alarmDays.text = activity.getAlarmSelectedDaysString(alarm.days)
alarmDays.text = getAlarmSelectedDaysString(alarm)
alarmDays.setTextColor(textColor)
alarmLabel.text = alarm.label
@@ -185,34 +184,38 @@ class AlarmsAdapter(
}
}
alarm.isToday() -> {
if (alarm.timeInMinutes <= getCurrentDayMinutes()) {
alarm.days = TOMORROW_BIT
binding.alarmDays.text =
resources.getString(org.fossify.commons.R.string.tomorrow)
}
activity.dbHelper.updateAlarm(alarm)
toggleAlarmInterface.alarmToggled(alarm.id, binding.alarmSwitch.isChecked)
}
alarm.isTomorrow() -> {
toggleAlarmInterface.alarmToggled(alarm.id, binding.alarmSwitch.isChecked)
}
// Unreachable zombie branch. Days are always set to a non-zero value.
binding.alarmSwitch.isChecked -> {
activity.toast(R.string.no_days_selected)
binding.alarmSwitch.isChecked = false
}
else -> {
updateNonRecurringAlarmDay(alarm)
activity.dbHelper.updateAlarm(alarm)
binding.alarmDays.text = getAlarmSelectedDaysString(
alarm = alarm, isEnabled = binding.alarmSwitch.isChecked
)
toggleAlarmInterface.alarmToggled(alarm.id, binding.alarmSwitch.isChecked)
}
}
}
private fun getAlarmSelectedDaysString(
alarm: Alarm, isEnabled: Boolean = alarm.isEnabled,
): String {
if (alarm.isRecurring()) {
return if (alarm.days == EVERY_DAY_BIT) {
activity.getString(org.fossify.commons.R.string.every_day)
} else {
// TODO: This does not respect config.firstDayOfWeek
activity.getSelectedDaysString(alarm.days)
}
}
return when {
!isEnabled -> resources.getString(R.string.not_scheduled)
alarm.isToday() -> resources.getString(org.fossify.commons.R.string.today)
else -> resources.getString(org.fossify.commons.R.string.tomorrow)
}
}
override fun onRowMoved(fromPosition: Int, toPosition: Int) {
alarms.swap(fromPosition, toPosition)
alarms.move(fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
saveAlarmsCustomOrder(alarms)
if (activity.config.alarmSort != SORT_BY_CUSTOM) {

View File

@@ -15,7 +15,6 @@ import org.fossify.clock.extensions.config
import org.fossify.clock.extensions.getFormattedDuration
import org.fossify.clock.extensions.hideTimerNotification
import org.fossify.clock.extensions.secondsToMillis
import org.fossify.clock.extensions.swap
import org.fossify.clock.models.Timer
import org.fossify.clock.models.TimerEvent
import org.fossify.clock.models.TimerState.Finished
@@ -31,6 +30,7 @@ import org.fossify.commons.extensions.beInvisibleIf
import org.fossify.commons.extensions.beVisibleIf
import org.fossify.commons.extensions.getColoredDrawableWithColor
import org.fossify.commons.extensions.getFormattedDuration
import org.fossify.commons.extensions.move
import org.fossify.commons.extensions.openNotificationSettings
import org.fossify.commons.helpers.SORT_BY_CUSTOM
import org.fossify.commons.interfaces.ItemMoveCallback
@@ -260,7 +260,7 @@ class TimerAdapter(
override fun onRowMoved(fromPosition: Int, toPosition: Int) {
val timers = currentList.toMutableList()
timers.swap(fromPosition, toPosition)
timers.move(fromPosition, toPosition)
submitList(timers)
saveAlarmsCustomOrder(ArrayList(timers))
if (simpleActivity.config.timerSort != SORT_BY_CUSTOM) {

View File

@@ -19,9 +19,8 @@ import org.fossify.clock.extensions.getFormattedTime
import org.fossify.clock.extensions.handleFullScreenNotificationsPermission
import org.fossify.clock.extensions.rotateWeekdays
import org.fossify.clock.helpers.PICK_AUDIO_FILE_INTENT_ID
import org.fossify.clock.helpers.TODAY_BIT
import org.fossify.clock.helpers.TOMORROW_BIT
import org.fossify.clock.helpers.getCurrentDayMinutes
import org.fossify.clock.helpers.updateNonRecurringAlarmDay
import org.fossify.clock.models.Alarm
import org.fossify.commons.dialogs.ConfirmationDialog
import org.fossify.commons.dialogs.SelectAlarmSoundDialog
@@ -178,13 +177,7 @@ class EditAlarmDialog(
return@setOnClickListener
}
if (alarm.days <= 0) {
alarm.days = if (alarm.timeInMinutes > getCurrentDayMinutes()) {
TODAY_BIT
} else {
TOMORROW_BIT
}
}
updateNonRecurringAlarmDay(alarm)
alarm.label = binding.editAlarm.value
alarm.isEnabled = true

View File

@@ -66,14 +66,12 @@ import org.fossify.commons.extensions.formatSecondsToTimeString
import org.fossify.commons.extensions.getDefaultAlarmSound
import org.fossify.commons.extensions.getLaunchIntent
import org.fossify.commons.extensions.getProperPrimaryColor
import org.fossify.commons.extensions.getSelectedDaysString
import org.fossify.commons.extensions.grantReadUriPermission
import org.fossify.commons.extensions.notificationManager
import org.fossify.commons.extensions.rotateLeft
import org.fossify.commons.extensions.showErrorToast
import org.fossify.commons.extensions.toInt
import org.fossify.commons.extensions.toast
import org.fossify.commons.helpers.EVERY_DAY_BIT
import org.fossify.commons.helpers.FRIDAY_BIT
import org.fossify.commons.helpers.MINUTE_SECONDS
import org.fossify.commons.helpers.MONDAY_BIT
@@ -562,15 +560,6 @@ fun Context.checkAlarmsWithDeletedSoundUri(uri: String) {
}
}
fun Context.getAlarmSelectedDaysString(bitMask: Int): String {
return when (bitMask) {
TODAY_BIT -> getString(org.fossify.commons.R.string.today)
TOMORROW_BIT -> getString(org.fossify.commons.R.string.tomorrow)
EVERY_DAY_BIT -> getString(org.fossify.commons.R.string.every_day)
else -> getSelectedDaysString(bitMask) // TODO: This does not respect config.firstDayOfWeek
}
}
fun Context.rotateWeekdays(days: List<Int>) = days.rotateLeft(config.firstDayOfWeek - 1)
fun Context.firstDayOrder(bitMask: Int): Int {
@@ -608,12 +597,3 @@ fun Context.startAlarmService(alarmId: Int) {
showErrorToast(e)
}
}
fun Context.stopAlarmService() {
try {
val serviceIntent = Intent(this, AlarmService::class.java)
stopService(serviceIntent)
} catch (e: Exception) {
showErrorToast(e)
}
}

View File

@@ -1,14 +0,0 @@
package org.fossify.clock.extensions
@Deprecated(
message = "Use the `move` extension function from commons",
replaceWith = ReplaceWith(
expression = "this.move(index1, index2)",
imports = ["org.fossify.commons.extensions.move"]
)
)
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
this[index1] = this[index2].also {
this[index2] = this[index1]
}
}

View File

@@ -2,16 +2,18 @@ package org.fossify.clock.helpers
import android.app.Application
import android.content.Context
import android.content.Intent
import org.fossify.clock.extensions.cancelAlarmClock
import org.fossify.clock.extensions.dbHelper
import org.fossify.clock.extensions.setupAlarmClock
import org.fossify.clock.extensions.showRemainingTimeMessage
import org.fossify.clock.extensions.startAlarmService
import org.fossify.clock.extensions.stopAlarmService
import org.fossify.clock.extensions.updateWidgets
import org.fossify.clock.models.Alarm
import org.fossify.clock.models.AlarmEvent
import org.fossify.clock.services.AlarmService
import org.fossify.commons.extensions.removeBit
import org.fossify.commons.extensions.showErrorToast
import org.fossify.commons.helpers.ensureBackgroundThread
import org.greenrobot.eventbus.EventBus
import java.util.Calendar
@@ -26,8 +28,10 @@ class AlarmController(
private val bus: EventBus,
) {
/**
* Reschedules all enabled alarms with the exception of one-time alarms that were scheduled
* for today (to avoid rescheduling skipped upcoming alarms, yeah).
* Reschedules all enabled alarms.
* Skips rescheduling one-time alarms that were set for today but whose time has already passed,
* and potentially upcoming alarms for today depending on the logic in `scheduleNextOccurrence`.
* NOTE: The handling of skipped upcoming alarms for today needs refinement.
*/
fun rescheduleEnabledAlarms() {
db.getEnabledAlarms().forEach {
@@ -40,7 +44,7 @@ class AlarmController(
}
/**
* Schedules the next occurrence of a repeating alarm based on its repetition rules.
* Schedules the next occurrence of the given alarm based on its properties (time, repetition).
*
* @param alarm The alarm to schedule.
* @param showToasts If true, a remaining time toast will be shown for the alarm.
@@ -92,8 +96,9 @@ class AlarmController(
}
/**
* Handles the triggering of an alarm, scheduling the next occurrence and starting the service
* for sounding the alarm.
* Handles the triggering of an alarm.
* If the alarm is repeating, it schedules the next occurrence immediately.
* Then, it starts the service for sounding the alarm.
*
* @param alarmId The ID of the alarm that was triggered.
*/
@@ -109,26 +114,31 @@ class AlarmController(
context.startAlarmService(alarmId)
}
/**
* Silences the currently ringing alarm by stopping the alarm service.
*/
fun silenceAlarm() {
stopAlarmService()
}
/**
* Dismisses an alarm that is currently ringing or has just finished ringing.
*
* - Stops the alarm sound/vibration service ([stopAlarmService]).
* - If the alarm is *not* repeating and the `disable` parameter is true, the alarm is
* disabled or deleted via [disableOrDeleteOneTimeAlarm].
* - If the alarm is *not* repeating, it is cancelled in the system scheduler and then
* disabled or deleted via [disableOrDeleteOneTimeAlarm].
*
* @param alarmId The ID of the alarm to dismiss.
* @param disable If true and the alarm is a one-time alarm, it will be disabled or deleted.
* This parameter has no effect on repeating alarms.
*/
fun stopAlarm(alarmId: Int, disable: Boolean = true) {
context.stopAlarmService()
fun stopAlarm(alarmId: Int) {
stopAlarmService()
bus.post(AlarmEvent.Stopped(alarmId))
ensureBackgroundThread {
val alarm = db.getAlarmWithId(alarmId)
// We don't reschedule alarms here.
if (alarm != null && !alarm.isRecurring() && disable) {
if (alarm != null && !alarm.isRecurring()) {
context.cancelAlarmClock(alarm)
disableOrDeleteOneTimeAlarm(alarm)
}
@@ -144,24 +154,22 @@ class AlarmController(
* - Schedules the alarm to ring again after [snoozeMinutes] using [setupAlarmClock]
* with a calculated future trigger time.
*
* TODO: This works but it is very rudimentary. Snoozed alarms should be tracked properly.
*
* @param alarmId The ID of the alarm to snooze.
* @param snoozeMinutes The number of minutes from now until the alarm should ring again.
*/
fun snoozeAlarm(alarmId: Int, snoozeMinutes: Int) {
context.stopAlarmService()
stopAlarmService()
bus.post(AlarmEvent.Stopped(alarmId))
ensureBackgroundThread {
val alarm = db.getAlarmWithId(alarmId)
// TODO: This works but it is very rudimentary. Snoozed alarms are not being tracked.
if (alarm != null) {
context.setupAlarmClock(
alarm = alarm,
triggerTimeMillis = Calendar.getInstance()
.apply { add(Calendar.MINUTE, snoozeMinutes) }
.timeInMillis
)
val triggerTimeMillis = Calendar.getInstance()
.apply { add(Calendar.MINUTE, snoozeMinutes) }
.timeInMillis
context.setupAlarmClock(alarm = alarm, triggerTimeMillis = triggerTimeMillis)
}
notifyObservers()
@@ -204,6 +212,15 @@ class AlarmController(
bus.post(AlarmEvent.Refresh)
}
private fun stopAlarmService() {
try {
val serviceIntent = Intent(context, AlarmService::class.java)
context.stopService(serviceIntent)
} catch (e: Exception) {
context.showErrorToast(e)
}
}
companion object {
@Volatile
private var instance: AlarmController? = null

View File

@@ -287,7 +287,7 @@ fun getTimeOfNextAlarm(alarmTimeInMinutes: Int, days: Int): Calendar? {
else -> {
val now = Calendar.getInstance()
repeat(8) {
val currentDay = (nextAlarmTime.get(Calendar.DAY_OF_WEEK) + 5) % 7
val currentDay = getDayNumber(nextAlarmTime.get(Calendar.DAY_OF_WEEK))
if (days.isBitSet(currentDay) && now < nextAlarmTime) {
return nextAlarmTime
} else {
@@ -298,3 +298,12 @@ fun getTimeOfNextAlarm(alarmTimeInMinutes: Int, days: Int): Calendar? {
}
}
}
fun updateNonRecurringAlarmDay(alarm: Alarm) {
if (alarm.isRecurring()) return
alarm.days = if (alarm.timeInMinutes > getCurrentDayMinutes()) {
TODAY_BIT
} else {
TOMORROW_BIT
}
}

View File

@@ -32,6 +32,7 @@
<string name="no_timers_found">No timers found</string>
<string name="add_timer">Add timer</string>
<string name="upcoming_alarm">Upcoming alarm</string>
<string name="not_scheduled">Not scheduled</string>
<!-- Timer -->
<string name="timers_notification_msg">Timers are running</string>