mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-07-13 17:51:57 +00:00
Schedule reminders logic updates
This commit is contained in:
parent
2b6e49f6f5
commit
19d04147d2
3 changed files with 215 additions and 68 deletions
|
|
@ -1,5 +1,7 @@
|
||||||
package com.habitrpg.android.habitica.extensions
|
package com.habitrpg.android.habitica.extensions
|
||||||
|
|
||||||
|
import com.habitrpg.android.habitica.models.tasks.Days
|
||||||
|
import com.habitrpg.shared.habitica.models.tasks.Frequency
|
||||||
import java.time.DayOfWeek
|
import java.time.DayOfWeek
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
|
|
@ -7,6 +9,7 @@ import java.time.ZonedDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.time.format.DateTimeFormatterBuilder
|
import java.time.format.DateTimeFormatterBuilder
|
||||||
import java.time.format.TextStyle
|
import java.time.format.TextStyle
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
import java.time.temporal.TemporalAccessor
|
import java.time.temporal.TemporalAccessor
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
@ -42,3 +45,63 @@ fun formatter(): DateTimeFormatter =
|
||||||
.append(DateTimeFormatter.ISO_LOCAL_TIME)
|
.append(DateTimeFormatter.ISO_LOCAL_TIME)
|
||||||
.appendPattern("[XX]")
|
.appendPattern("[XX]")
|
||||||
.toFormatter()
|
.toFormatter()
|
||||||
|
|
||||||
|
|
||||||
|
fun ZonedDateTime.matchesDailyInterval(startDate: ZonedDateTime, everyX: Int): Boolean {
|
||||||
|
val daysBetween = ChronoUnit.DAYS.between(startDate.toLocalDate(), this.toLocalDate())
|
||||||
|
return daysBetween % everyX == 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ZonedDateTime.matchesWeeklyInterval(startDate: ZonedDateTime, everyX: Int): Boolean {
|
||||||
|
val weeksBetween = ChronoUnit.WEEKS.between(startDate.toLocalDate(), this.toLocalDate())
|
||||||
|
return weeksBetween % everyX == 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ZonedDateTime.matchesMonthlyInterval(startDate: ZonedDateTime, everyX: Int, dayOfMonth: Int): Boolean {
|
||||||
|
val monthsBetween = ChronoUnit.MONTHS.between(startDate.toLocalDate(), this.toLocalDate())
|
||||||
|
return this.dayOfMonth == dayOfMonth && monthsBetween % everyX == 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ZonedDateTime.matchesYearlyInterval(startDate: ZonedDateTime, everyX: Int): Boolean {
|
||||||
|
val yearsBetween = ChronoUnit.YEARS.between(startDate.toLocalDate(), this.toLocalDate())
|
||||||
|
return yearsBetween % everyX == 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ZonedDateTime.matchesRepeatDays(repeatDays: Days?): Boolean {
|
||||||
|
repeatDays ?: return true // If no repeatDays specified, assume it matches
|
||||||
|
|
||||||
|
return when (this.dayOfWeek) {
|
||||||
|
DayOfWeek.MONDAY -> repeatDays.m
|
||||||
|
DayOfWeek.TUESDAY -> repeatDays.t
|
||||||
|
DayOfWeek.WEDNESDAY -> repeatDays.w
|
||||||
|
DayOfWeek.THURSDAY -> repeatDays.th
|
||||||
|
DayOfWeek.FRIDAY -> repeatDays.f
|
||||||
|
DayOfWeek.SATURDAY -> repeatDays.s
|
||||||
|
DayOfWeek.SUNDAY -> repeatDays.su
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Probably shouldn't be an extension function, but it's easier to test this way
|
||||||
|
fun ZonedDateTime.isReminderDue(reminderTime: ZonedDateTime, frequency: Frequency, everyX: Int, repeatDays: Days?, dayOfMonth: Int): Boolean {
|
||||||
|
// Check if the reminder is due based on the frequency and everyX
|
||||||
|
when (frequency) {
|
||||||
|
Frequency.DAILY -> {
|
||||||
|
if (!this.matchesDailyInterval(reminderTime, everyX)) return false
|
||||||
|
}
|
||||||
|
Frequency.WEEKLY -> {
|
||||||
|
if (!this.matchesWeeklyInterval(reminderTime, everyX)) return false
|
||||||
|
}
|
||||||
|
Frequency.MONTHLY -> {
|
||||||
|
if (!this.matchesMonthlyInterval(reminderTime, everyX, dayOfMonth)) return false
|
||||||
|
}
|
||||||
|
Frequency.YEARLY -> {
|
||||||
|
if (!this.matchesYearlyInterval(reminderTime, everyX)) return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the reminder is due based on the repeatDays
|
||||||
|
return this.matchesRepeatDays(repeatDays)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ import com.habitrpg.common.habitica.helpers.ExceptionHandler
|
||||||
import com.habitrpg.shared.habitica.HLogger
|
import com.habitrpg.shared.habitica.HLogger
|
||||||
import com.habitrpg.shared.habitica.LogLevel
|
import com.habitrpg.shared.habitica.LogLevel
|
||||||
import com.habitrpg.shared.habitica.models.tasks.TaskType
|
import com.habitrpg.shared.habitica.models.tasks.TaskType
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
|
@ -34,20 +36,41 @@ class TaskAlarmManager(
|
||||||
private var authenticationHandler: AuthenticationHandler
|
private var authenticationHandler: AuthenticationHandler
|
||||||
) {
|
) {
|
||||||
private val am: AlarmManager? = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
|
private val am: AlarmManager? = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
|
||||||
|
private val upcomingReminderOccurrencesToSchedule = 3
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules multiple alarms for each reminder associated with a given task.
|
||||||
|
*
|
||||||
|
* This method iterates through all reminders of a task and schedules multiple upcoming alarms for each.
|
||||||
|
* It determines the upcoming reminder times based on the reminder's configuration (like frequency, repeat days, etc.)
|
||||||
|
* and schedules an alarm for each of these times.
|
||||||
|
*
|
||||||
|
* For each reminder, it updates the reminder's time to each upcoming occurrence and then calls
|
||||||
|
* `setAlarmForRemindersItem` to handle the actual alarm scheduling. This ensures that each reminder
|
||||||
|
* is scheduled accurately according to its specified rules and times.
|
||||||
|
*
|
||||||
|
* This method is particularly useful for reminders that need to be repeated at regular intervals
|
||||||
|
* (e.g., daily, weekly) or on specific days, as it schedules multiple occurrences in advance.
|
||||||
|
*
|
||||||
|
* @param task The task for which the alarms are being set. Each reminder in the task's reminder list
|
||||||
|
* is processed to schedule the upcoming alarms.
|
||||||
|
*/
|
||||||
private fun setAlarmsForTask(task: Task) {
|
private fun setAlarmsForTask(task: Task) {
|
||||||
task.reminders?.let {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
for (reminder in it) {
|
task.reminders?.let { reminders ->
|
||||||
var currentReminder = reminder
|
for (reminder in reminders) {
|
||||||
if (task.type == TaskType.DAILY) {
|
val upcomingReminders = task.getNextReminderOccurrences(reminder, upcomingReminderOccurrencesToSchedule)
|
||||||
// Ensure that we set to the next available time
|
upcomingReminders?.forEachIndexed { index, reminderNextOccurrenceTime ->
|
||||||
currentReminder = this.setTimeForDailyReminder(currentReminder, task)
|
reminder?.time = reminderNextOccurrenceTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||||
|
setAlarmForRemindersItem(task, reminder, index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.setAlarmForRemindersItem(task, currentReminder)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun removeAlarmsForTask(task: Task) {
|
private fun removeAlarmsForTask(task: Task) {
|
||||||
task.reminders?.let {
|
task.reminders?.let {
|
||||||
for (reminder in it) {
|
for (reminder in it) {
|
||||||
|
|
@ -81,26 +104,34 @@ class TaskAlarmManager(
|
||||||
setAlarmsForTask(task)
|
setAlarmsForTask(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setTimeForDailyReminder(remindersItem: RemindersItem?, task: Task): RemindersItem? {
|
|
||||||
val newTime = task.getNextReminderOccurrence(remindersItem, context)
|
|
||||||
remindersItem?.time = newTime?.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
|
||||||
|
|
||||||
return remindersItem
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If reminderItem time is before now, a new reminder will not be created until the reminder passes.
|
* Schedules an alarm for a given reminder associated with a task.
|
||||||
* The exception to this is if the task & reminder was newly created for the same time,
|
*
|
||||||
* in which the alarm will be created -
|
* This method takes a task and its associated reminder item to schedule an alarm.
|
||||||
* which is indicated by first nextDue being null (As the alarm is created before the API returns nextDue times)
|
* It first checks if the reminder time is valid and not in the past. If the reminder time
|
||||||
|
* is valid, it prepares an intent for the alarm, uniquely identified by combining the reminder's ID
|
||||||
|
* and its scheduled time. This unique identifier ensures that each reminder occurrence is distinctly handled.
|
||||||
|
*
|
||||||
|
* If an alarm with the same identifier already exists, it is cancelled and replaced with the new one.
|
||||||
|
* This ensures that reminders are always up to date with their latest scheduled times.
|
||||||
|
*
|
||||||
|
* The alarm is scheduled to trigger at the exact time specified in the reminder. Upon triggering,
|
||||||
|
* it will send a broadcast to `TaskReceiver`, which should handle the reminder notification.
|
||||||
|
*
|
||||||
|
* @param reminderItemTask The task associated with the reminder.
|
||||||
|
* @param remindersItem The reminder item containing details like ID and the time for the reminder.
|
||||||
|
* If this is null, the method returns immediately without scheduling an alarm.
|
||||||
*/
|
*/
|
||||||
private fun setAlarmForRemindersItem(reminderItemTask: Task, remindersItem: RemindersItem?) {
|
private fun setAlarmForRemindersItem(reminderItemTask: Task, remindersItem: RemindersItem?, occurrenceIndex: Int) {
|
||||||
if (remindersItem == null) return
|
if (remindersItem == null) return
|
||||||
|
|
||||||
val now = ZonedDateTime.now().withZoneSameLocal(ZoneId.systemDefault())?.toInstant()
|
val now = ZonedDateTime.now().withZoneSameLocal(ZoneId.systemDefault())?.toInstant()
|
||||||
val reminderZonedTime = remindersItem.getLocalZonedDateTimeInstant()
|
val reminderZonedTime = remindersItem.getLocalZonedDateTimeInstant()
|
||||||
|
|
||||||
if (reminderZonedTime == null || reminderZonedTime.isBefore(now)) return
|
|
||||||
|
if (reminderZonedTime == null || reminderZonedTime.isBefore(now)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val intent = Intent(context, TaskReceiver::class.java)
|
val intent = Intent(context, TaskReceiver::class.java)
|
||||||
|
|
@ -108,7 +139,9 @@ class TaskAlarmManager(
|
||||||
intent.putExtra(TASK_NAME_INTENT_KEY, reminderItemTask.text)
|
intent.putExtra(TASK_NAME_INTENT_KEY, reminderItemTask.text)
|
||||||
intent.putExtra(TASK_ID_INTENT_KEY, reminderItemTask.id)
|
intent.putExtra(TASK_ID_INTENT_KEY, reminderItemTask.id)
|
||||||
|
|
||||||
val intentId = remindersItem.id?.hashCode() ?: (0 and 0xfffffff)
|
// Create a unique identifier based on remindersItem.id and the occurrence index
|
||||||
|
val intentId = (remindersItem.id?.hashCode() ?: 0) + occurrenceIndex
|
||||||
|
|
||||||
// Cancel alarm if already exists
|
// Cancel alarm if already exists
|
||||||
val previousSender = PendingIntent.getBroadcast(
|
val previousSender = PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
|
|
@ -128,7 +161,10 @@ class TaskAlarmManager(
|
||||||
withImmutableFlag(PendingIntent.FLAG_CANCEL_CURRENT)
|
withImmutableFlag(PendingIntent.FLAG_CANCEL_CURRENT)
|
||||||
)
|
)
|
||||||
|
|
||||||
setAlarm(context, reminderZonedTime.toEpochMilli(), sender)
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
setAlarm(context, reminderZonedTime.toEpochMilli(), sender)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeAlarmForRemindersItem(remindersItem: RemindersItem) {
|
private fun removeAlarmForRemindersItem(remindersItem: RemindersItem) {
|
||||||
|
|
@ -220,13 +256,16 @@ class TaskAlarmManager(
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
when (ex) {
|
when (ex) {
|
||||||
is IllegalStateException, is SecurityException -> {
|
is IllegalStateException, is SecurityException -> {
|
||||||
alarmManager?.setWindow(AlarmManager.RTC_WAKEUP, time, 60000, pendingIntent)
|
alarmManager?.setWindow(AlarmManager.RTC_WAKEUP, time, 600000, pendingIntent)
|
||||||
}
|
}
|
||||||
else -> throw ex
|
else -> {
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alarmManager?.setWindow(AlarmManager.RTC_WAKEUP, time, 60000, pendingIntent)
|
alarmManager?.setWindow(AlarmManager.RTC_WAKEUP, time, 600000, pendingIntent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package com.habitrpg.android.habitica.models.tasks
|
package com.habitrpg.android.habitica.models.tasks
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import com.habitrpg.android.habitica.R
|
import com.habitrpg.android.habitica.R
|
||||||
import com.habitrpg.android.habitica.extensions.dayOfWeekString
|
import com.habitrpg.android.habitica.extensions.isReminderDue
|
||||||
|
import com.habitrpg.android.habitica.extensions.matchesRepeatDays
|
||||||
import com.habitrpg.android.habitica.extensions.parseToZonedDateTime
|
import com.habitrpg.android.habitica.extensions.parseToZonedDateTime
|
||||||
import com.habitrpg.android.habitica.extensions.toZonedDateTime
|
import com.habitrpg.android.habitica.extensions.toZonedDateTime
|
||||||
import com.habitrpg.android.habitica.models.BaseMainObject
|
import com.habitrpg.android.habitica.models.BaseMainObject
|
||||||
|
|
@ -24,9 +24,9 @@ import io.realm.annotations.PrimaryKey
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
|
open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
|
||||||
|
|
@ -332,56 +332,97 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
|
||||||
fun checkIfDue(): Boolean = isDue == true
|
fun checkIfDue(): Boolean = isDue == true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When manually changing the alarm time, nextDue will provide the previously set upcoming alarm -
|
* Calculates a list of the next upcoming reminder occurrences based on the reminder's configuration.
|
||||||
* however if the alarm was changed to a upcoming time today (and not before the current time) AND the previous alarm
|
|
||||||
* set for today already passed, we want to check if the alarm repeat/days includes today as well.
|
|
||||||
* If so - use today's date to schedule an alarm. If not, use nextDue.
|
|
||||||
*
|
*
|
||||||
* Alarms automatically rescheduled -> nextDue used.
|
* This method determines the next few occurrences of a reminder, taking into account its start date,
|
||||||
* A alarm that already passed today and is rescheduled for a upcoming time today -> Schedule for today (instead of using nextDue)
|
* frequency, repetition pattern, and other settings. It generates a list of ZonedDateTime instances,
|
||||||
|
* each representing a future point in time when the reminder is due.
|
||||||
|
*
|
||||||
|
* The method iterates starting from the reminder's start date and checks each day to see if it matches
|
||||||
|
* the criteria for triggering the reminder (like matching the specified days of the week, frequency, etc.).
|
||||||
|
* Once a matching date is found, it's added to the list with the specific time of the reminder.
|
||||||
|
*
|
||||||
|
* This is useful for scheduling reminders that need to occur multiple times in the future according
|
||||||
|
* to a specific pattern (e.g., every Monday and Wednesday at 10 AM).
|
||||||
|
*
|
||||||
|
* @param remindersItem The reminder item containing the configuration for calculating occurrences.
|
||||||
|
* @param occurrences The number of upcoming reminder occurrences to calculate.
|
||||||
|
* @return A list of ZonedDateTime instances, each representing an upcoming reminder occurrence.
|
||||||
|
* Returns null if the reminder item is null or essential information for calculation is missing.
|
||||||
*/
|
*/
|
||||||
fun getNextReminderOccurrence(remindersItem: RemindersItem?, context: Context): ZonedDateTime? {
|
fun getNextReminderOccurrences(remindersItem: RemindersItem?, occurrences: Int): List<ZonedDateTime>? {
|
||||||
if (remindersItem == null) {
|
if (remindersItem == null) return null
|
||||||
return null
|
|
||||||
|
val reminderTime = remindersItem.time?.parseToZonedDateTime() ?: return null
|
||||||
|
val startDate = this.startDate?.toInstant()?.atZone(ZoneId.systemDefault()) ?: return null
|
||||||
|
val frequency = this.frequency ?: return null
|
||||||
|
val everyX = this.everyX ?: 1
|
||||||
|
val repeatDays = this.repeat
|
||||||
|
|
||||||
|
|
||||||
|
// Determine the starting point: either the start date or the current date if start date is in the past
|
||||||
|
var currentDate = ZonedDateTime.now().withZoneSameInstant(ZoneId.systemDefault())
|
||||||
|
if (startDate.isAfter(currentDate)) {
|
||||||
|
currentDate = startDate
|
||||||
}
|
}
|
||||||
|
|
||||||
val reminderTime = remindersItem.time?.parseToZonedDateTime()
|
val occurrencesList = mutableListOf<ZonedDateTime>()
|
||||||
val zonedDateTimeNow = ZonedDateTime.now()
|
|
||||||
|
|
||||||
// Check if the reminder is scheduled to repeat today
|
while (occurrencesList.size < occurrences) {
|
||||||
val repeatingDays = repeat?.dayStrings(context)
|
if (currentDate.isReminderDue(reminderTime, frequency, everyX, repeatDays, currentDate.dayOfMonth)) {
|
||||||
val isScheduledForToday = repeatingDays?.find { day -> day == zonedDateTimeNow.dayOfWeekString() }
|
occurrencesList.add(currentDate.withHour(reminderTime.hour).withMinute(reminderTime.minute))
|
||||||
if (isScheduledForToday != null) {
|
}
|
||||||
val currentDateTime = LocalDateTime.now()
|
|
||||||
val updatedDateTime: LocalDateTime = LocalDateTime.of(
|
// Increment currentDate based on the frequency
|
||||||
currentDateTime.year,
|
currentDate = when (frequency) {
|
||||||
currentDateTime.month,
|
Frequency.DAILY -> currentDate.plusDays(everyX.toLong())
|
||||||
currentDateTime.dayOfMonth,
|
Frequency.WEEKLY -> {
|
||||||
reminderTime?.hour ?: 0,
|
if (repeatDays?.hasAnyDaySelected() == true) {
|
||||||
reminderTime?.minute ?: 0
|
// Find the next day of the week that matches the player's selection
|
||||||
)
|
var nextDueDate = currentDate
|
||||||
return updatedDateTime.atZone(ZoneId.systemDefault())
|
while (!nextDueDate.matchesRepeatDays(repeatDays)) {
|
||||||
|
nextDueDate = nextDueDate.plusDays(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the number of weeks between the start date and the next due date
|
||||||
|
val weeksSinceStart = ChronoUnit.WEEKS.between(startDate.toLocalDate(), nextDueDate.toLocalDate())
|
||||||
|
|
||||||
|
// Check if the next due date falls in the correct week interval
|
||||||
|
if (weeksSinceStart % everyX != 0L) {
|
||||||
|
// If it doesn't, find the start of the next valid interval
|
||||||
|
val weeksToNextValidInterval = everyX - (weeksSinceStart % everyX)
|
||||||
|
nextDueDate = nextDueDate.plusWeeks(weeksToNextValidInterval)
|
||||||
|
|
||||||
|
while (!nextDueDate.matchesRepeatDays(repeatDays)) {
|
||||||
|
nextDueDate = nextDueDate.plusDays(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the next due date is in the future
|
||||||
|
val now = ZonedDateTime.now().withZoneSameInstant(ZoneId.systemDefault())
|
||||||
|
if (nextDueDate.isBefore(now)) {
|
||||||
|
nextDueDate = nextDueDate.plusWeeks(everyX.toLong())
|
||||||
|
while (!nextDueDate.matchesRepeatDays(repeatDays)) {
|
||||||
|
nextDueDate = nextDueDate.plusDays(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDate = nextDueDate
|
||||||
|
} else {
|
||||||
|
// If no specific days are selected, increment by the number of weeks
|
||||||
|
currentDate = currentDate.plusWeeks(everyX.toLong())
|
||||||
|
}
|
||||||
|
currentDate
|
||||||
|
}
|
||||||
|
Frequency.MONTHLY -> currentDate.plusMonths(everyX.toLong()).withDayOfMonth(startDate.dayOfMonth)
|
||||||
|
Frequency.YEARLY -> currentDate.plusYears(everyX.toLong()).withDayOfMonth(startDate.dayOfMonth).withMonth(startDate.monthValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// If the reminder is not scheduled to repeat today, use the first upcoming nextDue date
|
return occurrencesList
|
||||||
val today = LocalDate.now()
|
|
||||||
// Filter out the dates that are in the past
|
|
||||||
val futureNextDues = nextDue?.filter { nextDueDate -> nextDueDate.toInstant().atZone(ZonedDateTime.now().zone).toLocalDate().isAfter(today) }
|
|
||||||
val earliestFutureDate = futureNextDues?.minByOrNull { it }
|
|
||||||
if (earliestFutureDate != null) {
|
|
||||||
return earliestFutureDate.toInstant()
|
|
||||||
.atZone(ZonedDateTime.now().zone)
|
|
||||||
.withHour(reminderTime?.hour ?: 0)
|
|
||||||
.withMinute(reminderTime?.minute ?: 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// If there are no upcoming nextDue dates, use the first upcoming reminder time
|
|
||||||
return reminderTime
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun parseMarkdown() {
|
fun parseMarkdown() {
|
||||||
parsedText = MarkdownParser.parseMarkdown(text)
|
parsedText = MarkdownParser.parseMarkdown(text)
|
||||||
parsedNotes = MarkdownParser.parseMarkdown(notes)
|
parsedNotes = MarkdownParser.parseMarkdown(notes)
|
||||||
|
|
@ -585,6 +626,10 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Days.hasAnyDaySelected(): Boolean {
|
||||||
|
return m || t || w || th || f || s || su
|
||||||
|
}
|
||||||
|
|
||||||
fun getDaysOfMonth(): List<Int>? {
|
fun getDaysOfMonth(): List<Int>? {
|
||||||
if (daysOfMonth == null) {
|
if (daysOfMonth == null) {
|
||||||
val daysOfMonth = mutableListOf<Int>()
|
val daysOfMonth = mutableListOf<Int>()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue