mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-07-13 17:51:57 +00:00
Merge branch 'main' of https://github.com/HabitRPG/habitica-android
This commit is contained in:
commit
23dd2d6fd1
4 changed files with 209 additions and 76 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,23 @@ fun formatter(): DateTimeFormatter =
|
||||||
.append(DateTimeFormatter.ISO_LOCAL_TIME)
|
.append(DateTimeFormatter.ISO_LOCAL_TIME)
|
||||||
.appendPattern("[XX]")
|
.appendPattern("[XX]")
|
||||||
.toFormatter()
|
.toFormatter()
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.habitrpg.android.habitica.data.TaskRepository
|
import com.habitrpg.android.habitica.data.TaskRepository
|
||||||
import com.habitrpg.android.habitica.extensions.withImmutableFlag
|
import com.habitrpg.android.habitica.extensions.withImmutableFlag
|
||||||
|
|
@ -17,6 +18,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,24 +37,48 @@ 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) {
|
|
||||||
task.reminders?.let {
|
fun removeAlarmsForTask(task: Task) {
|
||||||
for (reminder in it) {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
this.removeAlarmForRemindersItem(reminder)
|
task.reminders?.let { reminders ->
|
||||||
|
// Remove not only the immediate reminder, but also the next however many (upcomingReminderOccurrencesToSchedule) reminders
|
||||||
|
reminders.forEachIndexed { index, reminder ->
|
||||||
|
removeAlarmForRemindersItem(reminder, index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,35 +108,43 @@ class TaskAlarmManager(
|
||||||
setAlarmsForTask(task)
|
setAlarmsForTask(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setTimeForDailyReminder(remindersItem: RemindersItem?, task: Task): RemindersItem? {
|
|
||||||
val newTime = task.getNextReminderOccurrence(remindersItem, context)
|
|
||||||
remindersItem?.time = newTime?.withZoneSameLocal(ZoneId.systemDefault())?.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
|
||||||
|
|
||||||
val now = ZonedDateTime.now().withZoneSameLocal(ZoneId.systemDefault())?.toInstant()
|
val now = ZonedDateTime.now().withZoneSameLocal(ZoneId.systemDefault())?.toInstant()
|
||||||
val zonedTime = remindersItem?.getLocalZonedDateTimeInstant()
|
val reminderZonedTime = remindersItem.getLocalZonedDateTimeInstant()
|
||||||
if (remindersItem == null ||
|
|
||||||
(reminderItemTask.type == TaskType.DAILY && zonedTime?.isBefore(now) == true && reminderItemTask.nextDue?.firstOrNull() != null) ||
|
if (reminderZonedTime == null || reminderZonedTime.isBefore(now)) {
|
||||||
(reminderItemTask.type == TaskType.TODO && zonedTime?.isBefore(now) == true || zonedTime == null)
|
|
||||||
) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val intent = Intent(context, TaskReceiver::class.java)
|
val intent = Intent(context, TaskReceiver::class.java)
|
||||||
intent.action = remindersItem.id
|
intent.action = remindersItem.id
|
||||||
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,
|
||||||
|
|
@ -129,13 +164,16 @@ class TaskAlarmManager(
|
||||||
withImmutableFlag(PendingIntent.FLAG_CANCEL_CURRENT)
|
withImmutableFlag(PendingIntent.FLAG_CANCEL_CURRENT)
|
||||||
)
|
)
|
||||||
|
|
||||||
setAlarm(context, zonedTime.toEpochMilli(), sender)
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
setAlarm(context, reminderZonedTime.toEpochMilli(), sender)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeAlarmForRemindersItem(remindersItem: RemindersItem) {
|
private fun removeAlarmForRemindersItem(remindersItem: RemindersItem, occurrenceIndex: Int? = null) {
|
||||||
val intent = Intent(context, TaskReceiver::class.java)
|
val intent = Intent(context, TaskReceiver::class.java)
|
||||||
intent.action = remindersItem.id
|
intent.action = remindersItem.id
|
||||||
val intentId = remindersItem.id?.hashCode() ?: (0 and 0xfffffff)
|
val intentId = if (occurrenceIndex != null) (remindersItem.id?.hashCode() ?: (0 and 0xfffffff)) + occurrenceIndex else (remindersItem.id?.hashCode() ?: (0 and 0xfffffff))
|
||||||
val sender = PendingIntent.getBroadcast(
|
val sender = PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
intentId,
|
intentId,
|
||||||
|
|
@ -218,16 +256,20 @@ class TaskAlarmManager(
|
||||||
// For SDK >= Android 12, allows batching of reminders
|
// For SDK >= Android 12, allows batching of reminders
|
||||||
try {
|
try {
|
||||||
alarmManager?.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pendingIntent)
|
alarmManager?.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pendingIntent)
|
||||||
|
Log.d("TaskAlarmManager", "setAlarm: Scheduling for $time using setAndAllowWhileIdle")
|
||||||
} 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,11 @@
|
||||||
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.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 +23,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,48 +331,111 @@ 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 reminderTime = remindersItem.time?.parseToZonedDateTime()
|
val now = ZonedDateTime.now().withZoneSameInstant(ZoneId.systemDefault())
|
||||||
val zonedDateTimeNow = ZonedDateTime.now()
|
var startDate = this.startDate?.toInstant()?.atZone(ZoneId.systemDefault()) ?: return null
|
||||||
return if (nextDue?.firstOrNull() != null && (!isDisplayedActive || reminderTime?.isBefore(zonedDateTimeNow) == true)
|
val frequency = this.frequency ?: return null
|
||||||
) {
|
val everyX = this.everyX ?: 1
|
||||||
val repeatingDays = repeat?.dayStrings(context)
|
val repeatDays = this.repeat
|
||||||
val isScheduledForToday = repeatingDays?.find { day -> day == zonedDateTimeNow.dayOfWeekString() }
|
startDate = startDate.withHour(reminderTime.hour).withMinute(reminderTime.minute)
|
||||||
if (isScheduledForToday != null) {
|
|
||||||
val currentDateTime = LocalDateTime.now()
|
var dateTimeOccurenceToSchedule = ZonedDateTime.now().withZoneSameInstant(ZoneId.systemDefault())
|
||||||
val updatedDateTime: LocalDateTime = LocalDateTime.of(
|
|
||||||
currentDateTime.year,
|
val occurrencesList = mutableListOf<ZonedDateTime>()
|
||||||
currentDateTime.month,
|
|
||||||
currentDateTime.dayOfMonth,
|
while (occurrencesList.size < occurrences) {
|
||||||
reminderTime?.hour ?: 0,
|
// Increment currentDate based on the frequency
|
||||||
reminderTime?.minute ?: 0
|
dateTimeOccurenceToSchedule = when (frequency) {
|
||||||
)
|
Frequency.DAILY -> {
|
||||||
updatedDateTime.atZone(ZoneId.systemDefault())
|
dateTimeOccurenceToSchedule = if (dateTimeOccurenceToSchedule.isBefore(startDate)) {
|
||||||
} else {
|
startDate
|
||||||
val nextDate = LocalDateTime.ofInstant(nextDue?.firstOrNull()?.toInstant(), ZoneId.systemDefault())
|
} else {
|
||||||
val updatedDateTime: LocalDateTime = LocalDateTime.of(
|
dateTimeOccurenceToSchedule.plusDays(everyX.toLong()).withHour(reminderTime.hour).withMinute(reminderTime.minute)
|
||||||
nextDate.year,
|
}
|
||||||
nextDate.month,
|
dateTimeOccurenceToSchedule
|
||||||
nextDate.dayOfMonth,
|
}
|
||||||
reminderTime?.hour ?: 0,
|
Frequency.WEEKLY -> {
|
||||||
reminderTime?.minute ?: 0
|
// Set to start date if current date is earlier
|
||||||
)
|
if (dateTimeOccurenceToSchedule.isBefore(startDate)) {
|
||||||
updatedDateTime.atZone(ZoneId.systemDefault())
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return reminderTime
|
occurrencesList.add(dateTimeOccurenceToSchedule)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return occurrencesList
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseMarkdown() {
|
fun parseMarkdown() {
|
||||||
|
|
@ -579,6 +641,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>()
|
||||||
|
|
|
||||||
|
|
@ -835,6 +835,8 @@ class TaskFormActivity : BaseActivity() {
|
||||||
task?.id?.let {
|
task?.id?.let {
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
taskRepository.deleteTask(it)
|
taskRepository.deleteTask(it)
|
||||||
|
val taskCopy = task
|
||||||
|
taskCopy?.let { taskAlarmManager.removeAlarmsForTask(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finish()
|
finish()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue