From 1907d675ada1a5187715d5504554191c262dd57a Mon Sep 17 00:00:00 2001 From: Fiz <34069775+Hafizzle@users.noreply.github.com> Date: Wed, 7 Feb 2024 12:34:04 -0500 Subject: [PATCH] To-Do reminder not showing fix (#2057) * Reminder logic updates Check for upcoming reminder and/or if reminder is happening today. Also - error on the side of showing a reminder * Error on the side of showing reminder (Even if reminder time passed) * Schedule reminders logic updates * Fixes for reminders (Week & Year) * Remove unused extensions * Refactor weekly reminder scheduling logic for specific days intervals Refactor weekly reminder scheduling logic to handle specific days and intervals correctly * Remove alarms/reminders on task deletion * Handle weekly same day reminder that already passed * Handle if no days are selected for a weekly * To-Do reminder not showing fix Schedule single reminder instance occurrence for todo, and check if reminder is from todo when actually setting the reminder (To avoid scheduling returning early due to checking Daily reminder fields) --------- Co-authored-by: Phillip Thelen --- .../habitica/helpers/TaskAlarmManager.kt | 6 +- .../android/habitica/models/tasks/Task.kt | 125 +++++++++--------- 2 files changed, 68 insertions(+), 63 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt index 6bc16793e..e46158ac7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskAlarmManager.kt @@ -59,9 +59,13 @@ class TaskAlarmManager( */ private fun setAlarmsForTask(task: Task) { CoroutineScope(Dispatchers.IO).launch { + val reminderOccurencesToSchedule = if (task.type == TaskType.TODO) { 1 } else { + // For dailies, we schedule multiple reminders in advance + upcomingReminderOccurrencesToSchedule + } task.reminders?.let { reminders -> for (reminder in reminders) { - val upcomingReminders = task.getNextReminderOccurrences(reminder, upcomingReminderOccurrencesToSchedule) + val upcomingReminders = task.getNextReminderOccurrences(reminder, reminderOccurencesToSchedule) upcomingReminders?.forEachIndexed { index, reminderNextOccurrenceTime -> reminder?.time = reminderNextOccurrenceTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) setAlarmForRemindersItem(task, reminder, index) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt index e6e7f47c6..cc020df93 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.kt @@ -354,6 +354,14 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { if (remindersItem == null) return null val reminderTime = remindersItem.time?.parseToZonedDateTime() ?: return null + var dateTimeOccurenceToSchedule = ZonedDateTime.now().withZoneSameInstant(ZoneId.systemDefault()) + val occurrencesList = mutableListOf() + + // If the reminder is a todo, only schedule sole dueDate/time occurrence. Otherwise, schedule multiple occurrences in advance + if (this.type == TaskType.TODO) { + occurrencesList.add(this.dueDate?.toZonedDateTime()?.withHour(reminderTime.hour)?.withMinute(reminderTime.minute) ?: return null) + return occurrencesList + } val now = ZonedDateTime.now().withZoneSameInstant(ZoneId.systemDefault()) var startDate = this.startDate?.toInstant()?.atZone(ZoneId.systemDefault()) ?: return null val frequency = this.frequency ?: return null @@ -361,81 +369,74 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask { val repeatDays = this.repeat startDate = startDate.withHour(reminderTime.hour).withMinute(reminderTime.minute) - var dateTimeOccurenceToSchedule = ZonedDateTime.now().withZoneSameInstant(ZoneId.systemDefault()) - - val occurrencesList = mutableListOf() - while (occurrencesList.size < occurrences) { // Increment currentDate based on the frequency - try { - dateTimeOccurenceToSchedule = when (frequency) { - Frequency.DAILY -> { - dateTimeOccurenceToSchedule = if (dateTimeOccurenceToSchedule.isBefore(startDate)) { - startDate - } else { - dateTimeOccurenceToSchedule.plusDays(everyX.toLong()).withHour(reminderTime.hour).withMinute(reminderTime.minute) - } - dateTimeOccurenceToSchedule + dateTimeOccurenceToSchedule = when (frequency) { + Frequency.DAILY -> { + dateTimeOccurenceToSchedule = if (dateTimeOccurenceToSchedule.isBefore(startDate)) { + startDate + } else { + dateTimeOccurenceToSchedule.plusDays(everyX.toLong()).withHour(reminderTime.hour).withMinute(reminderTime.minute) } - Frequency.WEEKLY -> { - // Set to start date if current date is earlier - if (dateTimeOccurenceToSchedule.isBefore(startDate)) { - dateTimeOccurenceToSchedule = startDate - } else if (repeatDays?.hasAnyDaySelected() == true) { + dateTimeOccurenceToSchedule + } + Frequency.WEEKLY -> { + // Set to start date if current date is earlier + if (dateTimeOccurenceToSchedule.isBefore(startDate)) { + dateTimeOccurenceToSchedule = startDate + } else if (repeatDays?.hasAnyDaySelected() == true) { - var nextDueDate = dateTimeOccurenceToSchedule.withHour(reminderTime.hour).withMinute(reminderTime.minute) - // If the next due date already happened for today, increment it by one day. Otherwise, it will be scheduled for today. - if (nextDueDate.isBefore(now) && occurrencesList.size == 0) { - nextDueDate = nextDueDate.plusDays(1) - } + var nextDueDate = dateTimeOccurenceToSchedule.withHour(reminderTime.hour).withMinute(reminderTime.minute) + // If the next due date already happened for today, increment it by one day. Otherwise, it will be scheduled for today. + if (nextDueDate.isBefore(now) && occurrencesList.size == 0) { + nextDueDate = nextDueDate.plusDays(1) + } - // If the reminder being scheduled is not the first iteration of the reminder, increment it by one day - if (occurrencesList.size > 0) { - nextDueDate = nextDueDate.plusDays(1) - } + // If the reminder being scheduled is not the first iteration of the reminder, increment it by one day + if (occurrencesList.size > 0) { + nextDueDate = nextDueDate.plusDays(1) + } + while (!nextDueDate.matchesRepeatDays(repeatDays)) { + nextDueDate = nextDueDate.plusDays(1).withHour(reminderTime.hour).withMinute(reminderTime.minute) + } + // Calculate weeks since start and adjust for the correct interval + val weeksSinceStart = ChronoUnit.WEEKS.between(startDate.toLocalDate(), nextDueDate.toLocalDate()) + if (weeksSinceStart % everyX != 0L) { + val weeksToNextValidInterval = everyX - (weeksSinceStart % everyX) + nextDueDate = nextDueDate.plusWeeks(weeksToNextValidInterval) + // Find the exact next due day within the valid interval while (!nextDueDate.matchesRepeatDays(repeatDays)) { nextDueDate = nextDueDate.plusDays(1).withHour(reminderTime.hour).withMinute(reminderTime.minute) } - // Calculate weeks since start and adjust for the correct interval - val weeksSinceStart = ChronoUnit.WEEKS.between(startDate.toLocalDate(), nextDueDate.toLocalDate()) - if (everyX > 0 && weeksSinceStart % everyX != 0L) { - val weeksToNextValidInterval = everyX - (weeksSinceStart % everyX) - nextDueDate = nextDueDate.plusWeeks(weeksToNextValidInterval) - // Find the exact next due day within the valid interval - while (!nextDueDate.matchesRepeatDays(repeatDays)) { - nextDueDate = nextDueDate.plusDays(1).withHour(reminderTime.hour).withMinute(reminderTime.minute) - } - } + } - dateTimeOccurenceToSchedule = nextDueDate - } - // Set time to the reminder time - dateTimeOccurenceToSchedule = dateTimeOccurenceToSchedule.withHour(reminderTime.hour).withMinute(reminderTime.minute) - dateTimeOccurenceToSchedule - } - - Frequency.MONTHLY -> { - dateTimeOccurenceToSchedule = if (dateTimeOccurenceToSchedule.isBefore(startDate)) { - startDate - } else { - dateTimeOccurenceToSchedule.plusMonths(everyX.toLong()).withDayOfMonth(startDate.dayOfMonth).withHour(reminderTime.hour).withMinute(reminderTime.minute) - } - dateTimeOccurenceToSchedule - } - Frequency.YEARLY -> { - dateTimeOccurenceToSchedule = if (dateTimeOccurenceToSchedule.isBefore(startDate)) { - startDate - } else { - dateTimeOccurenceToSchedule.plusYears(everyX.toLong()).withDayOfMonth(startDate.dayOfMonth).withMonth(startDate.monthValue).withHour(reminderTime.hour).withMinute(reminderTime.minute) - } - dateTimeOccurenceToSchedule + dateTimeOccurenceToSchedule = nextDueDate } + // Set time to the reminder time + dateTimeOccurenceToSchedule = dateTimeOccurenceToSchedule.withHour(reminderTime.hour).withMinute(reminderTime.minute) + dateTimeOccurenceToSchedule + } + + Frequency.MONTHLY -> { + dateTimeOccurenceToSchedule = if (dateTimeOccurenceToSchedule.isBefore(startDate)) { + startDate + } else { + dateTimeOccurenceToSchedule.plusMonths(everyX.toLong()).withDayOfMonth(startDate.dayOfMonth).withHour(reminderTime.hour).withMinute(reminderTime.minute) + } + dateTimeOccurenceToSchedule + } + Frequency.YEARLY -> { + dateTimeOccurenceToSchedule = if (dateTimeOccurenceToSchedule.isBefore(startDate)) { + startDate + } else { + dateTimeOccurenceToSchedule.plusYears(everyX.toLong()).withDayOfMonth(startDate.dayOfMonth).withMonth(startDate.monthValue).withHour(reminderTime.hour).withMinute(reminderTime.minute) + } + dateTimeOccurenceToSchedule } - occurrencesList.add(dateTimeOccurenceToSchedule) - } catch (_: DateTimeException) { - // Invalid date like feb 30th } + + occurrencesList.add(dateTimeOccurenceToSchedule) }