Add task reminders feature

This commit is contained in:
Naveen 2022-06-27 23:18:10 +05:30
parent e9fa8307d3
commit 3933f8d2d3
7 changed files with 295 additions and 33 deletions

View file

@ -1169,7 +1169,7 @@ class EventActivity : SimpleActivity() {
private fun storeEvent(wasRepeatable: Boolean) {
if (mEvent.id == null || mEvent.id == null) {
eventsHelper.insertEvent(mEvent, true, true) {
eventsHelper.insertEvent(mEvent, addToCalDAV = true, showToasts = true) {
hideKeyboard()
if (DateTime.now().isAfter(mEventStartDateTime.millis)) {
@ -1187,7 +1187,7 @@ class EventActivity : SimpleActivity() {
}
} else {
hideKeyboard()
eventsHelper.updateEvent(mEvent, true, true) {
eventsHelper.updateEvent(mEvent, updateAtCalDAV = true, showToasts = true) {
finish()
}
}
@ -1221,7 +1221,7 @@ class EventActivity : SimpleActivity() {
id = null
}
eventsHelper.insertEvent(mEvent, true, true) {
eventsHelper.insertEvent(mEvent, addToCalDAV = true, showToasts = true) {
finish()
}
}
@ -1230,7 +1230,7 @@ class EventActivity : SimpleActivity() {
2 -> {
ensureBackgroundThread {
eventsHelper.addEventRepeatLimit(mEvent.id!!, mEventOccurrenceTS)
eventsHelper.updateEvent(mEvent, true, true) {
eventsHelper.updateEvent(mEvent, updateAtCalDAV = true, showToasts = true) {
finish()
}
}

View file

@ -8,13 +8,16 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.WindowManager
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import com.simplemobiletools.calendar.pro.R
import com.simplemobiletools.calendar.pro.dialogs.ReminderWarningDialog
import com.simplemobiletools.calendar.pro.dialogs.SelectEventTypeDialog
import com.simplemobiletools.calendar.pro.extensions.*
import com.simplemobiletools.calendar.pro.helpers.*
import com.simplemobiletools.calendar.pro.helpers.Formatter
import com.simplemobiletools.calendar.pro.models.Event
import com.simplemobiletools.calendar.pro.models.Reminder
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
@ -26,6 +29,14 @@ class TaskActivity : SimpleActivity() {
private var mEventTypeId = REGULAR_EVENT_TYPE_ID
private lateinit var mTaskDateTime: DateTime
private lateinit var mTask: Event
private var mIsAllDayEvent = false
private var mReminder1Minutes = REMINDER_OFF
private var mReminder2Minutes = REMINDER_OFF
private var mReminder3Minutes = REMINDER_OFF
private var mReminder1Type = REMINDER_NOTIFICATION
private var mReminder2Type = REMINDER_NOTIFICATION
private var mReminder3Type = REMINDER_NOTIFICATION
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -85,6 +96,10 @@ class TaskActivity : SimpleActivity() {
putSerializable(TASK, mTask)
putLong(START_TS, mTaskDateTime.seconds())
putLong(EVENT_TYPE_ID, mEventTypeId)
putInt(REMINDER_1_MINUTES, mReminder1Minutes)
putInt(REMINDER_2_MINUTES, mReminder2Minutes)
putInt(REMINDER_3_MINUTES, mReminder3Minutes)
}
}
@ -100,6 +115,10 @@ class TaskActivity : SimpleActivity() {
mTask = getSerializable(TASK) as Event
mTaskDateTime = Formatter.getDateTimeFromTS(getLong(START_TS))
mEventTypeId = getLong(EVENT_TYPE_ID)
mReminder1Minutes = getInt(REMINDER_1_MINUTES)
mReminder2Minutes = getInt(REMINDER_2_MINUTES)
mReminder3Minutes = getInt(REMINDER_3_MINUTES)
}
updateEventType()
@ -123,12 +142,18 @@ class TaskActivity : SimpleActivity() {
}
} else {
mTask = Event(null)
config.apply {
mReminder1Minutes = if (usePreviousEventReminders && lastEventReminderMinutes1 >= -1) lastEventReminderMinutes1 else defaultReminder1
mReminder2Minutes = if (usePreviousEventReminders && lastEventReminderMinutes2 >= -1) lastEventReminderMinutes2 else defaultReminder2
mReminder3Minutes = if (usePreviousEventReminders && lastEventReminderMinutes3 >= -1) lastEventReminderMinutes3 else defaultReminder3
}
if (savedInstanceState == null) {
setupNewTask()
}
}
task_all_day.setOnCheckedChangeListener { compoundButton, isChecked -> toggleAllDay(isChecked) }
task_all_day.setOnCheckedChangeListener { _, isChecked -> toggleAllDay(isChecked) }
task_all_day_holder.setOnClickListener {
task_all_day.toggle()
}
@ -137,10 +162,27 @@ class TaskActivity : SimpleActivity() {
task_time.setOnClickListener { setupTime() }
event_type_holder.setOnClickListener { showEventTypeDialog() }
event_reminder_1.setOnClickListener {
handleNotificationAvailability {
if (config.wasAlarmWarningShown) {
showReminder1Dialog()
} else {
ReminderWarningDialog(this) {
config.wasAlarmWarningShown = true
showReminder1Dialog()
}
}
}
}
event_reminder_2.setOnClickListener { showReminder2Dialog() }
event_reminder_3.setOnClickListener { showReminder3Dialog() }
if (savedInstanceState == null) {
updateEventType()
updateDateText()
updateTimeText()
updateReminderTexts()
}
}
@ -150,6 +192,13 @@ class TaskActivity : SimpleActivity() {
updateActionBarTitle(getString(R.string.edit_task))
mEventTypeId = mTask.eventType
mReminder1Minutes = mTask.reminder1Minutes
mReminder2Minutes = mTask.reminder2Minutes
mReminder3Minutes = mTask.reminder3Minutes
mReminder1Type = mTask.reminder1Type
mReminder2Type = mTask.reminder2Type
mReminder3Type = mTask.reminder3Type
task_title.setText(mTask.title)
task_description.setText(mTask.description)
task_all_day.isChecked = mTask.getIsAllDay()
@ -177,6 +226,34 @@ class TaskActivity : SimpleActivity() {
return
}
val reminders = getReminders()
if (!task_all_day.isChecked) {
if ((reminders.getOrNull(2)?.minutes ?: 0) < -1) {
reminders.removeAt(2)
}
if ((reminders.getOrNull(1)?.minutes ?: 0) < -1) {
reminders.removeAt(1)
}
if ((reminders.getOrNull(0)?.minutes ?: 0) < -1) {
reminders.removeAt(0)
}
}
val reminder1 = reminders.getOrNull(0) ?: Reminder(REMINDER_OFF, REMINDER_NOTIFICATION)
val reminder2 = reminders.getOrNull(1) ?: Reminder(REMINDER_OFF, REMINDER_NOTIFICATION)
val reminder3 = reminders.getOrNull(2) ?: Reminder(REMINDER_OFF, REMINDER_NOTIFICATION)
config.apply {
if (usePreviousEventReminders) {
lastEventReminderMinutes1 = reminder1.minutes
lastEventReminderMinutes2 = reminder2.minutes
lastEventReminderMinutes3 = reminder3.minutes
}
}
config.lastUsedLocalEventTypeId = mEventTypeId
mTask.apply {
startTS = mTaskDateTime.withSecondOfMinute(0).withMillisOfSecond(0).seconds()
@ -187,11 +264,25 @@ class TaskActivity : SimpleActivity() {
lastUpdated = System.currentTimeMillis()
eventType = mEventTypeId
type = TYPE_TASK
reminder1Minutes = mReminder1Minutes
reminder1Type = reminder1Minutes
reminder2Minutes = mReminder2Minutes
reminder2Type = reminder2Minutes
reminder3Minutes = mReminder3Minutes
reminder3Type = reminder3Minutes
}
ensureBackgroundThread {
EventsHelper(this).insertTask(mTask) {
EventsHelper(this).insertTask(mTask, true) {
hideKeyboard()
if (DateTime.now().isAfter(mTaskDateTime.millis)) {
if (mTask.repeatInterval == 0 && mTask.getReminders().any { it.type == REMINDER_NOTIFICATION }) {
notifyEvent(mTask)
}
}
finish()
}
}
@ -223,12 +314,12 @@ class TaskActivity : SimpleActivity() {
private fun setupDate() {
hideKeyboard()
val datepicker = DatePickerDialog(
val datePicker = DatePickerDialog(
this, getDatePickerDialogTheme(), dateSetListener, mTaskDateTime.year, mTaskDateTime.monthOfYear - 1, mTaskDateTime.dayOfMonth
)
datepicker.datePicker.firstDayOfWeek = if (config.isSundayFirst) Calendar.SUNDAY else Calendar.MONDAY
datepicker.show()
datePicker.datePicker.firstDayOfWeek = if (config.isSundayFirst) Calendar.SUNDAY else Calendar.MONDAY
datePicker.show()
}
private fun setupTime() {
@ -238,11 +329,11 @@ class TaskActivity : SimpleActivity() {
).show()
}
private val dateSetListener = DatePickerDialog.OnDateSetListener { view, year, monthOfYear, dayOfMonth ->
private val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
dateSet(year, monthOfYear, dayOfMonth)
}
private val timeSetListener = TimePickerDialog.OnTimeSetListener { view, hourOfDay, minute ->
private val timeSetListener = TimePickerDialog.OnTimeSetListener { _, hourOfDay, minute ->
timeSet(hourOfDay, minute)
}
@ -300,9 +391,93 @@ class TaskActivity : SimpleActivity() {
}
}
private fun updateReminderTexts() {
updateReminder1Text()
updateReminder2Text()
updateReminder3Text()
}
private fun updateReminder1Text() {
event_reminder_1.text = getFormattedMinutes(mReminder1Minutes)
}
private fun updateReminder2Text() {
event_reminder_2.apply {
beGoneIf(event_reminder_2.isGone() && mReminder1Minutes == REMINDER_OFF)
if (mReminder2Minutes == REMINDER_OFF) {
text = resources.getString(R.string.add_another_reminder)
alpha = 0.4f
} else {
text = getFormattedMinutes(mReminder2Minutes)
alpha = 1f
}
}
}
private fun updateReminder3Text() {
event_reminder_3.apply {
beGoneIf(event_reminder_3.isGone() && (mReminder2Minutes == REMINDER_OFF || mReminder1Minutes == REMINDER_OFF))
if (mReminder3Minutes == REMINDER_OFF) {
text = resources.getString(R.string.add_another_reminder)
alpha = 0.4f
} else {
text = getFormattedMinutes(mReminder3Minutes)
alpha = 1f
}
}
}
private fun handleNotificationAvailability(callback: () -> Unit) {
if (NotificationManagerCompat.from(this).areNotificationsEnabled()) {
callback()
} else {
ConfirmationDialog(this, messageId = R.string.notifications_disabled, positive = R.string.ok, negative = 0) {
callback()
}
}
}
private fun showReminder1Dialog() {
showPickSecondsDialogHelper(mReminder1Minutes, showDuringDayOption = mIsAllDayEvent) {
mReminder1Minutes = if (it == -1 || it == 0) it else it / 60
updateReminderTexts()
}
}
private fun showReminder2Dialog() {
showPickSecondsDialogHelper(mReminder2Minutes, showDuringDayOption = mIsAllDayEvent) {
mReminder2Minutes = if (it == -1 || it == 0) it else it / 60
updateReminderTexts()
}
}
private fun showReminder3Dialog() {
showPickSecondsDialogHelper(mReminder3Minutes, showDuringDayOption = mIsAllDayEvent) {
mReminder3Minutes = if (it == -1 || it == 0) it else it / 60
updateReminderTexts()
}
}
private fun getReminders(): ArrayList<Reminder> {
var reminders = arrayListOf(
Reminder(mReminder1Minutes, mReminder1Type),
Reminder(mReminder2Minutes, mReminder2Type),
Reminder(mReminder3Minutes, mReminder3Type)
)
reminders = reminders.filter { it.minutes != REMINDER_OFF }.sortedBy { it.minutes }.toMutableList() as ArrayList<Reminder>
return reminders
}
private fun showEventTypeDialog() {
hideKeyboard()
SelectEventTypeDialog(this, mEventTypeId, false, true, false, true) {
SelectEventTypeDialog(
activity = this,
currEventType = mEventTypeId,
showCalDAVCalendars = false,
showNewEventTypeOption = true,
addLastUsedOneAsFirstOption = false,
showOnlyWritable = true
) {
mEventTypeId = it.id!!
updateEventType()
}
@ -322,7 +497,11 @@ class TaskActivity : SimpleActivity() {
private fun updateColors() {
updateTextColors(task_scrollview)
task_time_image.applyColorFilter(getProperTextColor())
event_type_image.applyColorFilter(getProperTextColor())
val textColor = getProperTextColor()
arrayOf(
task_time_image, event_reminder_image, event_type_image
).forEach {
it.applyColorFilter(textColor)
}
}
}

View file

@ -95,7 +95,7 @@ fun Context.updateDateWidget() {
}
fun Context.scheduleAllEvents() {
val events = eventsDB.getEventsAtReboot(getNowSeconds())
val events = eventsDB.getEventsOrTasksAtReboot(getNowSeconds())
events.forEach {
scheduleNextEventReminder(it, false)
}
@ -116,9 +116,9 @@ fun Context.scheduleNextEventReminder(event: Event, showToasts: Boolean) {
val now = getNowSeconds()
val reminderSeconds = validReminders.reversed().map { it.minutes * 60 }
eventsHelper.getEvents(now, now + YEAR, event.id!!, false) {
if (it.isNotEmpty()) {
for (curEvent in it) {
eventsHelper.getEvents(now, now + YEAR, event.id!!, false) { events ->
if (events.isNotEmpty()) {
for (curEvent in events) {
for (curReminder in reminderSeconds) {
if (curEvent.getEventStartTS() - curReminder > now) {
scheduleEventIn((curEvent.getEventStartTS() - curReminder) * 1000L, curEvent, showToasts)
@ -196,9 +196,11 @@ fun Context.getRepetitionText(seconds: Int) = when (seconds) {
}
fun Context.notifyRunningEvents() {
eventsHelper.getRunningEvents().filter { it.getReminders().any { it.type == REMINDER_NOTIFICATION } }.forEach {
notifyEvent(it)
}
eventsHelper.getRunningEventsOrTasks()
.filter { it.getReminders().any { reminder -> reminder.type == REMINDER_NOTIFICATION } }
.forEach {
notifyEvent(it)
}
}
fun Context.notifyEvent(originalEvent: Event) {

View file

@ -123,9 +123,10 @@ class EventsHelper(val context: Context) {
callback?.invoke(event.id!!)
}
fun insertTask(task: Event, callback: () -> Unit) {
eventsDB.insertOrUpdate(task)
fun insertTask(task: Event, showToasts: Boolean, callback: () -> Unit) {
task.id = eventsDB.insertOrUpdate(task)
context.updateWidgets()
context.scheduleNextEventReminder(task, showToasts)
callback()
}
@ -295,7 +296,7 @@ class EventsHelper(val context: Context) {
events.addAll(
if (eventId == -1L) {
eventsDB.getOneTimeEventsFromTo(toTS, fromTS).toMutableList() as ArrayList<Event>
eventsDB.getOneTimeEventsOrTasksFromTo(toTS, fromTS).toMutableList() as ArrayList<Event>
} else {
eventsDB.getOneTimeEventFromToWithId(eventId, toTS, fromTS).toMutableList() as ArrayList<Event>
}
@ -481,9 +482,9 @@ class EventsHelper(val context: Context) {
return events
}
fun getRunningEvents(): List<Event> {
fun getRunningEventsOrTasks(): List<Event> {
val ts = getNowSeconds()
val events = eventsDB.getOneTimeEventsFromTo(ts, ts).toMutableList() as ArrayList<Event>
val events = eventsDB.getOneTimeEventsOrTasksFromTo(ts, ts).toMutableList() as ArrayList<Event>
events.addAll(getRepeatableEventsFor(ts, ts))
return events
}

View file

@ -27,16 +27,16 @@ interface EventsDao {
@Query("SELECT * FROM events WHERE import_id = :importId AND type = $TYPE_EVENT")
fun getEventWithImportId(importId: String): Event?
@Query("SELECT * FROM events WHERE start_ts <= :toTS AND end_ts >= :fromTS AND repeat_interval = 0 AND type = $TYPE_EVENT")
fun getOneTimeEventsFromTo(toTS: Long, fromTS: Long): List<Event>
@Query("SELECT * FROM events WHERE start_ts <= :toTS AND end_ts >= :fromTS AND repeat_interval = 0 AND (type = $TYPE_EVENT OR type = $TYPE_TASK)")
fun getOneTimeEventsOrTasksFromTo(toTS: Long, fromTS: Long): List<Event>
@Query("SELECT * FROM events WHERE start_ts <= :toTS AND start_ts >= :fromTS AND event_type IN (:eventTypeIds) AND type = $TYPE_TASK")
fun getTasksFromTo(fromTS: Long, toTS: Long, eventTypeIds: List<Long>): List<Event>
@Query("SELECT * FROM events WHERE id = :id AND start_ts <= :toTS AND end_ts >= :fromTS AND repeat_interval = 0 AND type = $TYPE_EVENT")
@Query("SELECT * FROM events WHERE id = :id AND start_ts <= :toTS AND end_ts >= :fromTS AND repeat_interval = 0 AND (type = $TYPE_EVENT OR type = $TYPE_TASK)")
fun getOneTimeEventFromToWithId(id: Long, toTS: Long, fromTS: Long): List<Event>
@Query("SELECT * FROM events WHERE start_ts <= :toTS AND end_ts >= :fromTS AND start_ts != 0 AND repeat_interval = 0 AND event_type IN (:eventTypeIds) AND type = $TYPE_EVENT")
@Query("SELECT * FROM events WHERE start_ts <= :toTS AND end_ts >= :fromTS AND start_ts != 0 AND repeat_interval = 0 AND event_type IN (:eventTypeIds) AND (type = $TYPE_EVENT OR type = $TYPE_TASK)")
fun getOneTimeEventsFromToWithTypes(toTS: Long, fromTS: Long, eventTypeIds: List<Long>): List<Event>
@Query("SELECT * FROM events WHERE end_ts > :toTS AND repeat_interval = 0 AND event_type IN (:eventTypeIds) AND type = $TYPE_EVENT")
@ -76,8 +76,8 @@ interface EventsDao {
fun getEventsWithIds(ids: List<Long>): List<Event>
//val selection = "$COL_REMINDER_MINUTES != -1 AND ($COL_START_TS > ? OR $COL_REPEAT_INTERVAL != 0) AND $COL_START_TS != 0"
@Query("SELECT * FROM events WHERE reminder_1_minutes != -1 AND (start_ts > :currentTS OR repeat_interval != 0) AND start_ts != 0 AND type = $TYPE_EVENT")
fun getEventsAtReboot(currentTS: Long): List<Event>
@Query("SELECT * FROM events WHERE reminder_1_minutes != -1 AND (start_ts > :currentTS OR repeat_interval != 0) AND start_ts != 0 AND (type = $TYPE_EVENT OR type = $TYPE_TASK)")
fun getEventsOrTasksAtReboot(currentTS: Long): List<Event>
@Query("SELECT id FROM events")
fun getEventIds(): List<Long>

View file

@ -72,6 +72,7 @@
android:layout_marginStart="@dimen/small_margin"
android:layout_toEndOf="@+id/task_time_image"
android:background="?attr/selectableItemBackground"
android:paddingStart="@dimen/zero"
android:paddingTop="@dimen/normal_margin"
android:paddingEnd="@dimen/normal_margin"
android:paddingBottom="@dimen/normal_margin">
@ -113,10 +114,88 @@
tools:text="00:00" />
<ImageView
android:id="@+id/event_caldav_calendar_divider"
android:id="@+id/event_date_time_divider"
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_below="@+id/task_date"
android:layout_marginTop="@dimen/medium_margin"
android:layout_marginBottom="@dimen/medium_margin"
android:background="@color/divider_grey"
android:importantForAccessibility="no" />
<ImageView
android:id="@+id/event_reminder_image"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_below="@+id/event_date_time_divider"
android:layout_alignTop="@+id/event_reminder_1"
android:layout_alignBottom="@+id/event_reminder_1"
android:layout_marginStart="@dimen/normal_margin"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_bell_vector" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/event_reminder_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/event_date_time_divider"
android:layout_marginStart="@dimen/small_margin"
android:layout_toEndOf="@+id/event_reminder_image"
android:background="?attr/selectableItemBackground"
android:ellipsize="end"
android:lines="1"
android:paddingStart="@dimen/zero"
android:paddingTop="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/activity_margin"
android:textSize="@dimen/day_text_size"
tools:text="@string/add_another_reminder" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/event_reminder_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/event_reminder_1"
android:layout_alignStart="@+id/event_reminder_1"
android:alpha="0.4"
android:background="?attr/selectableItemBackground"
android:ellipsize="end"
android:lines="1"
android:paddingStart="@dimen/zero"
android:paddingTop="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/activity_margin"
android:text="@string/add_another_reminder"
android:textSize="@dimen/day_text_size"
android:visibility="gone"
tools:text="@string/add_another_reminder" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/event_reminder_3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/event_reminder_2"
android:layout_alignStart="@+id/event_reminder_1"
android:alpha="0.4"
android:background="?attr/selectableItemBackground"
android:ellipsize="end"
android:lines="1"
android:paddingStart="@dimen/zero"
android:paddingTop="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/activity_margin"
android:text="@string/add_another_reminder"
android:textSize="@dimen/day_text_size"
android:visibility="gone"
tools:text="@string/add_another_reminder" />
<ImageView
android:id="@+id/event_caldav_calendar_divider"
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_below="@+id/event_reminder_3"
android:layout_marginTop="@dimen/medium_margin"
android:layout_marginBottom="@dimen/medium_margin"
android:background="@color/divider_grey"
android:importantForAccessibility="no" />

View file

@ -38,4 +38,5 @@
<dimen name="quick_filter_min_width">88dp</dimen>
<dimen name="quick_filter_active_line_size">6dp</dimen>
<dimen name="quick_filter_inactive_line_size">2dp</dimen>
<dimen name="zero">0dp</dimen>
</resources>