diff --git a/app/src/main/kotlin/org/fossify/clock/activities/AlarmActivity.kt b/app/src/main/kotlin/org/fossify/clock/activities/AlarmActivity.kt index 4ebb6e2c..a893fcc6 100644 --- a/app/src/main/kotlin/org/fossify/clock/activities/AlarmActivity.kt +++ b/app/src/main/kotlin/org/fossify/clock/activities/AlarmActivity.kt @@ -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, diff --git a/app/src/main/kotlin/org/fossify/clock/activities/SnoozeReminderActivity.kt b/app/src/main/kotlin/org/fossify/clock/activities/SnoozeReminderActivity.kt index 7439e73c..37f630e8 100644 --- a/app/src/main/kotlin/org/fossify/clock/activities/SnoozeReminderActivity.kt +++ b/app/src/main/kotlin/org/fossify/clock/activities/SnoozeReminderActivity.kt @@ -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, diff --git a/app/src/main/kotlin/org/fossify/clock/adapters/AlarmsAdapter.kt b/app/src/main/kotlin/org/fossify/clock/adapters/AlarmsAdapter.kt index 6f6e34d8..c29d72fe 100644 --- a/app/src/main/kotlin/org/fossify/clock/adapters/AlarmsAdapter.kt +++ b/app/src/main/kotlin/org/fossify/clock/adapters/AlarmsAdapter.kt @@ -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) { diff --git a/app/src/main/kotlin/org/fossify/clock/adapters/TimerAdapter.kt b/app/src/main/kotlin/org/fossify/clock/adapters/TimerAdapter.kt index 5940cff9..052bb3aa 100644 --- a/app/src/main/kotlin/org/fossify/clock/adapters/TimerAdapter.kt +++ b/app/src/main/kotlin/org/fossify/clock/adapters/TimerAdapter.kt @@ -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) { diff --git a/app/src/main/kotlin/org/fossify/clock/dialogs/EditAlarmDialog.kt b/app/src/main/kotlin/org/fossify/clock/dialogs/EditAlarmDialog.kt index f73bfaa7..dfcccfc1 100644 --- a/app/src/main/kotlin/org/fossify/clock/dialogs/EditAlarmDialog.kt +++ b/app/src/main/kotlin/org/fossify/clock/dialogs/EditAlarmDialog.kt @@ -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 diff --git a/app/src/main/kotlin/org/fossify/clock/extensions/Context.kt b/app/src/main/kotlin/org/fossify/clock/extensions/Context.kt index 00819e1a..169ee725 100644 --- a/app/src/main/kotlin/org/fossify/clock/extensions/Context.kt +++ b/app/src/main/kotlin/org/fossify/clock/extensions/Context.kt @@ -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) = 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) - } -} diff --git a/app/src/main/kotlin/org/fossify/clock/extensions/MutableList.kt b/app/src/main/kotlin/org/fossify/clock/extensions/MutableList.kt deleted file mode 100644 index 3937e0d4..00000000 --- a/app/src/main/kotlin/org/fossify/clock/extensions/MutableList.kt +++ /dev/null @@ -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 MutableList.swap(index1: Int, index2: Int) { - this[index1] = this[index2].also { - this[index2] = this[index1] - } -} diff --git a/app/src/main/kotlin/org/fossify/clock/helpers/AlarmController.kt b/app/src/main/kotlin/org/fossify/clock/helpers/AlarmController.kt index c99e77d4..607078a8 100644 --- a/app/src/main/kotlin/org/fossify/clock/helpers/AlarmController.kt +++ b/app/src/main/kotlin/org/fossify/clock/helpers/AlarmController.kt @@ -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 diff --git a/app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt b/app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt index de11c7f4..5ed5776b 100644 --- a/app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt +++ b/app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt @@ -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 + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c8b71b38..d49f5a04 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -32,6 +32,7 @@ No timers found Add timer Upcoming alarm + Not scheduled Timers are running