Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import org.fossify.clock.extensions.isBitSet
import org.fossify.clock.extensions.secondsToMillis
import org.fossify.clock.extensions.timerHelper
import org.fossify.clock.helpers.DEFAULT_ALARM_MINUTES
import org.fossify.clock.helpers.TODAY_BIT
import org.fossify.clock.helpers.TOMORROW_BIT
import org.fossify.clock.helpers.UPCOMING_ALARM_NOTIFICATION_ID
import org.fossify.clock.helpers.getBitForCalendarDay
import org.fossify.clock.helpers.getCurrentDayMinutes
import org.fossify.clock.helpers.getDateFromTimeInMinutes
import org.fossify.clock.helpers.getEpochDayFromMillis
import org.fossify.clock.helpers.getTodayBit
import org.fossify.clock.helpers.getTomorrowBit
import org.fossify.clock.models.Alarm
Expand All @@ -42,7 +42,6 @@ import org.fossify.commons.helpers.SILENT
import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.commons.models.AlarmSound
import org.greenrobot.eventbus.EventBus
import java.util.concurrent.TimeUnit

class IntentHandlerActivity : SimpleActivity() {
companion object {
Expand Down Expand Up @@ -114,22 +113,21 @@ class IntentHandlerActivity : SimpleActivity() {

// We don't want to accidentally edit existing alarm, so allow reuse only when skipping UI
if (hasExtra(AlarmClock.EXTRA_HOUR) && skipUi) {
var daysToCompare = weekDays
val timeInMinutes = hour * 60 + minute
if (weekDays <= 0) {
daysToCompare = if (timeInMinutes > getCurrentDayMinutes()) {
TODAY_BIT
val scheduledDateToCompare = if (weekDays <= 0) getDateFromTimeInMinutes(timeInMinutes) else 0L
val existingAlarm = dbHelper.getAlarms().firstOrNull {
val sameScheduling = if (weekDays > 0) {
it.days == weekDays
} else {
TOMORROW_BIT
!it.isRecurring() && it.getScheduledDateEpochDay() == scheduledDateToCompare
}
}
val existingAlarm = dbHelper.getAlarms().firstOrNull {
it.days == daysToCompare
&& it.vibrate == vibrate
&& it.soundTitle == soundToUse.title
&& it.soundUri == soundToUse.uri
&& it.label == (message ?: "")
&& it.timeInMinutes == timeInMinutes

sameScheduling
&& it.vibrate == vibrate
&& it.soundTitle == soundToUse.title
&& it.soundUri == soundToUse.uri
&& it.label == (message ?: "")
&& it.timeInMinutes == timeInMinutes
}

if (existingAlarm != null && !existingAlarm.isEnabled) {
Expand All @@ -156,11 +154,8 @@ class IntentHandlerActivity : SimpleActivity() {
} else {
newAlarm.timeInMinutes = hour * 60 + minute
if (newAlarm.days <= 0) {
newAlarm.days = if (newAlarm.timeInMinutes > getCurrentDayMinutes()) {
TODAY_BIT
} else {
TOMORROW_BIT
}
newAlarm.days = 0
newAlarm.scheduledDate = getDateFromTimeInMinutes(newAlarm.timeInMinutes)
newAlarm.oneShot = true
}

Expand Down Expand Up @@ -269,20 +264,23 @@ class IntentHandlerActivity : SimpleActivity() {

AlarmClock.ALARM_SEARCH_MODE_NEXT -> {
val next = alarmManager.nextAlarmClock
val calendar = java.util.Calendar.getInstance().apply {
timeInMillis = next.triggerTime
}
val timeInMinutes =
TimeUnit.MILLISECONDS.toMinutes(next.triggerTime).toInt()
calendar.get(java.util.Calendar.HOUR_OF_DAY) * 60 +
calendar.get(java.util.Calendar.MINUTE)
val nextEpochDay = getEpochDayFromMillis(next.triggerTime)
val dayBitToLookFor = if (timeInMinutes <= getCurrentDayMinutes()) {
getTomorrowBit()
} else {
getTodayBit()
}
val dayToLookFor = if (timeInMinutes <= getCurrentDayMinutes()) {
TOMORROW_BIT
} else {
TODAY_BIT
}
alarms = alarms.filter {
it.timeInMinutes == timeInMinutes && (it.days.isBitSet(dayBitToLookFor) || it.days == dayToLookFor)
it.timeInMinutes == timeInMinutes && (
(it.isRecurring() && it.days.isBitSet(dayBitToLookFor))
|| (!it.isRecurring() && it.getScheduledDateEpochDay() == nextEpochDay)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ 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.getFormattedDate
import org.fossify.clock.extensions.getFormattedTime
import org.fossify.clock.helpers.getCalendarFromEpochDay
import org.fossify.clock.helpers.updateNonRecurringAlarmDay
import org.fossify.clock.interfaces.ToggleAlarmInterface
import org.fossify.clock.models.Alarm
Expand Down Expand Up @@ -209,6 +211,10 @@ class AlarmsAdapter(

return when {
!isEnabled -> resources.getString(R.string.not_scheduled)
alarm.getScheduledDateEpochDay() > 0L -> {
activity.getFormattedDate(getCalendarFromEpochDay(alarm.getScheduledDateEpochDay()))
}

alarm.isToday() -> resources.getString(org.fossify.commons.R.string.today)
else -> resources.getString(org.fossify.commons.R.string.tomorrow)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import org.fossify.clock.extensions.checkAlarmsWithDeletedSoundUri
import org.fossify.clock.extensions.colorCompoundDrawable
import org.fossify.clock.extensions.config
import org.fossify.clock.extensions.dbHelper
import org.fossify.clock.extensions.getFormattedDate
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.getCurrentDayMinutes
import org.fossify.clock.helpers.getCalendarFromEpochDay
import org.fossify.clock.helpers.getDateFromTimeInMinutes
import org.fossify.clock.helpers.updateNonRecurringAlarmDay
import org.fossify.clock.models.Alarm
import org.fossify.commons.dialogs.ConfirmationDialog
Expand Down Expand Up @@ -139,6 +141,7 @@ class EditAlarmDialog(
day.setOnClickListener {
if (!alarm.isRecurring()) {
alarm.days = 0
alarm.scheduledDate = 0L
}

val selectDay = alarm.days and bitmask == 0
Expand Down Expand Up @@ -216,6 +219,7 @@ class EditAlarmDialog(
alarm.soundUri = lastConfig.soundUri
alarm.timeInMinutes = lastConfig.timeInMinutes
alarm.vibrate = lastConfig.vibrate
alarm.scheduledDate = lastConfig.scheduledDate
}
}
}
Expand All @@ -226,6 +230,9 @@ class EditAlarmDialog(

private fun timePicked(hours: Int, minutes: Int) {
alarm.timeInMinutes = hours * 60 + minutes
if (!alarm.isRecurring()) {
alarm.scheduledDate = 0L
}
updateAlarmTime()
}

Expand All @@ -240,13 +247,9 @@ class EditAlarmDialog(

private fun checkDaylessAlarm() {
if (!alarm.isRecurring()) {
val textId = if (alarm.timeInMinutes > getCurrentDayMinutes()) {
org.fossify.commons.R.string.today
} else {
org.fossify.commons.R.string.tomorrow
}

binding.editAlarmDaylessLabel.text = "(${activity.getString(textId)})"
val epochDay = alarm.scheduledDate.takeIf { it > 0L } ?: getDateFromTimeInMinutes(alarm.timeInMinutes)
val calendar = getCalendarFromEpochDay(epochDay)
binding.editAlarmDaylessLabel.text = "(${activity.getFormattedDate(calendar)})"
}
binding.editAlarmDaylessLabel.beVisibleIf(!alarm.isRecurring())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import org.fossify.clock.extensions.cancelAlarmClock
import org.fossify.clock.extensions.config
import org.fossify.clock.extensions.createNewAlarm
import org.fossify.clock.extensions.dbHelper
import org.fossify.clock.extensions.firstDayOrder
import org.fossify.clock.extensions.handleFullScreenNotificationsPermission
import org.fossify.clock.extensions.updateWidgets
import org.fossify.clock.helpers.DEFAULT_ALARM_MINUTES
import org.fossify.clock.helpers.SORT_BY_ALARM_TIME
import org.fossify.clock.helpers.SORT_BY_DATE_AND_TIME
import org.fossify.clock.helpers.getTomorrowBit
import org.fossify.clock.helpers.getTimeOfNextAlarm
import org.fossify.clock.helpers.getTomorrowEpochDay
import org.fossify.clock.interfaces.ToggleAlarmInterface
import org.fossify.clock.models.Alarm
import org.fossify.clock.models.AlarmEvent
Expand Down Expand Up @@ -81,7 +81,8 @@ class AlarmFragment : Fragment(), ToggleAlarmInterface {
alarmFab.setOnClickListener {
val newAlarm = root.context.createNewAlarm(DEFAULT_ALARM_MINUTES, 0)
newAlarm.isEnabled = true
newAlarm.days = getTomorrowBit()
newAlarm.days = 0
newAlarm.scheduledDate = getTomorrowEpochDay()
openEditAlarm(newAlarm)
}
}
Expand All @@ -103,11 +104,7 @@ class AlarmFragment : Fragment(), ToggleAlarmInterface {
when (safeContext.config.alarmSort) {
SORT_BY_ALARM_TIME -> newAlarms.sortBy { it.timeInMinutes }
SORT_BY_DATE_CREATED -> newAlarms.sortBy { it.id }
SORT_BY_DATE_AND_TIME -> newAlarms.sortWith(compareBy<Alarm> {
safeContext.firstDayOrder(it.days)
}.thenBy {
it.timeInMinutes
})
SORT_BY_DATE_AND_TIME -> newAlarms.sortBy { getTimeOfNextAlarm(it)?.timeInMillis ?: Long.MAX_VALUE }

SORT_BY_CUSTOM -> {
val customAlarmsSortOrderString = activity?.config?.alarmsCustomSorting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ class AlarmController(
) {
/**
* 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`.
* For one-time alarms, "today" / "tomorrow" are stored as relative sentinels.
* Normalize them against the current clock before rescheduling so a saved
* "tomorrow" alarm does not get pushed out by another full day after boot,
* package replacement, or a manual time change.
*/
fun rescheduleEnabledAlarms() {
db.getEnabledAlarms().forEach {
// TODO: Skipped upcoming alarms are being *rescheduled* here.
if (!it.isToday() || it.timeInMinutes > getCurrentDayMinutes()) {
scheduleNextOccurrence(it, false)
normalizeRescheduledAlarm(it)?.let { normalizedAlarm ->
scheduleNextOccurrence(normalizedAlarm, false)
}
}
}
Expand Down Expand Up @@ -193,6 +194,33 @@ class AlarmController(
}
}

private fun normalizeRescheduledAlarm(alarm: Alarm): Alarm? {
if (alarm.isRecurring()) {
return alarm
}

if (alarm.hasAbsoluteDate()) {
return alarm.takeIf { getTimeOfNextAlarm(it) != null }
}

val currentMinutes = getCurrentDayMinutes()
return when {
alarm.days == TODAY_BIT && alarm.timeInMinutes > currentMinutes -> {
alarm.copy(days = 0, scheduledDate = getCurrentEpochDay())
}

alarm.days == TOMORROW_BIT && alarm.timeInMinutes > currentMinutes -> {
alarm.copy(days = 0, scheduledDate = getTomorrowEpochDay())
}

alarm.days == TOMORROW_BIT -> {
alarm.copy(days = 0, scheduledDate = getCurrentEpochDay())
}

else -> null
}
}

private fun scheduleNextAlarm(alarm: Alarm, showToast: Boolean = false) {
val triggerTimeMillis = getTimeOfNextAlarm(alarm)?.timeInMillis ?: return
context.setupAlarmClock(alarm = alarm, triggerTimeMillis = triggerTimeMillis)
Expand Down
65 changes: 59 additions & 6 deletions app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import org.fossify.commons.helpers.THURSDAY_BIT
import org.fossify.commons.helpers.TUESDAY_BIT
import org.fossify.commons.helpers.WEDNESDAY_BIT
import org.fossify.commons.helpers.isPiePlus
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import java.util.Calendar
import java.util.Date
import java.util.TimeZone
Expand Down Expand Up @@ -164,6 +167,33 @@ fun getTomorrowBit(): Int {
return 1 shl dayOfWeek
}

fun getCurrentEpochDay(): Long = LocalDate.now().toEpochDay()

fun getTomorrowEpochDay(): Long = LocalDate.now().plusDays(1).toEpochDay()

fun getEpochDayFromMillis(timeInMillis: Long): Long {
return Instant.ofEpochMilli(timeInMillis).atZone(ZoneId.systemDefault()).toLocalDate().toEpochDay()
}

fun getDateFromTimeInMinutes(timeInMinutes: Int): Long {
return if (timeInMinutes > getCurrentDayMinutes()) {
getCurrentEpochDay()
} else {
getTomorrowEpochDay()
}
}

fun getCalendarFromEpochDay(epochDay: Long): Calendar {
val timeInMillis = LocalDate.ofEpochDay(epochDay)
.atStartOfDay(ZoneId.systemDefault())
.toInstant()
.toEpochMilli()

return Calendar.getInstance().apply {
this.timeInMillis = timeInMillis
}
}

fun getTodayBit(): Int {
val calendar = Calendar.getInstance()
val dayOfWeek = getDayNumber(calendar.get(Calendar.DAY_OF_WEEK))
Expand Down Expand Up @@ -274,7 +304,14 @@ fun getAllTimeZones() = arrayListOf(
)

fun getTimeOfNextAlarm(alarm: Alarm): Calendar? {
return getTimeOfNextAlarm(alarm.timeInMinutes, alarm.days)
return if (alarm.isRecurring()) {
getTimeOfNextAlarm(alarm.timeInMinutes, alarm.days)
} else {
when {
alarm.hasAbsoluteDate() -> getTimeOfNextAlarm(alarm.timeInMinutes, alarm.scheduledDate)
else -> getTimeOfNextAlarm(alarm.timeInMinutes, alarm.days)
}
}
}

fun getTimeOfNextAlarm(alarmTimeInMinutes: Int, days: Int): Calendar? {
Expand Down Expand Up @@ -303,11 +340,27 @@ fun getTimeOfNextAlarm(alarmTimeInMinutes: Int, days: Int): Calendar? {
}
}

fun getTimeOfNextAlarm(alarmTimeInMinutes: Int, epochDay: Long): Calendar? {
if (epochDay <= 0L) {
return null
}

val nextAlarmTime = getCalendarFromEpochDay(epochDay).apply {
set(Calendar.HOUR_OF_DAY, alarmTimeInMinutes / 60)
set(Calendar.MINUTE, alarmTimeInMinutes % 60)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}

return nextAlarmTime.takeIf { Calendar.getInstance() < it }
}

fun updateNonRecurringAlarmDay(alarm: Alarm) {
if (alarm.isRecurring()) return
alarm.days = if (alarm.timeInMinutes > getCurrentDayMinutes()) {
TODAY_BIT
} else {
TOMORROW_BIT
if (alarm.isRecurring()) {
alarm.scheduledDate = 0L
return
}

alarm.days = 0
alarm.scheduledDate = getDateFromTimeInMinutes(alarm.timeInMinutes)
}
Loading
Loading