From 09c46f5b88ac207944e9b014b5702ecab0fd99ec Mon Sep 17 00:00:00 2001 From: Felix Wiemuth <533601+felixwiemuth@users.noreply.github.com> Date: Fri, 16 Sep 2022 14:23:14 +0200 Subject: [PATCH] Persist notifications - Persist the map of active notifications on every change and reload it on application startup. - Reshow all active notifications on application startup using the original reminder due time. --- .../org/isoron/uhabits/HabitsApplication.kt | 1 + .../org/isoron/uhabits/inject/HabitsModule.kt | 5 ++- .../isoron/uhabits/core/models/Timestamp.kt | 2 + .../uhabits/core/preferences/Preferences.kt | 23 ++++++++++ .../uhabits/core/ui/NotificationTray.kt | 44 ++++++++++++++++--- 5 files changed, 68 insertions(+), 7 deletions(-) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/HabitsApplication.kt b/uhabits-android/src/main/java/org/isoron/uhabits/HabitsApplication.kt index 4f00f4d1f..965fab31f 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/HabitsApplication.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/HabitsApplication.kt @@ -94,6 +94,7 @@ class HabitsApplication : Application() { taskRunner.execute { reminderScheduler.scheduleAll() widgetUpdater.updateWidgets() + notificationTray.reshowAll() } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsModule.kt b/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsModule.kt index c7b6843d0..a95e610dd 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsModule.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsModule.kt @@ -72,9 +72,10 @@ class HabitsModule(dbFile: File) { taskRunner: TaskRunner, commandRunner: CommandRunner, preferences: Preferences, - screen: AndroidNotificationTray + screen: AndroidNotificationTray, + habitList: HabitList ): NotificationTray { - return NotificationTray(taskRunner, commandRunner, preferences, screen) + return NotificationTray(taskRunner, commandRunner, preferences, screen, habitList) } @Provides diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Timestamp.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Timestamp.kt index 2233237c0..74e7e9d10 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Timestamp.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Timestamp.kt @@ -18,6 +18,7 @@ */ package org.isoron.uhabits.core.models +import kotlinx.serialization.Serializable import org.isoron.platform.time.LocalDate import org.isoron.uhabits.core.utils.DateFormats.Companion.getCSVDateFormat import org.isoron.uhabits.core.utils.DateFormats.Companion.getDialogDateFormat @@ -29,6 +30,7 @@ import java.util.Date import java.util.GregorianCalendar import java.util.TimeZone +@Serializable data class Timestamp(var unixTime: Long) : Comparable { constructor(cal: GregorianCalendar) : this(cal.timeInMillis) diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/preferences/Preferences.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/preferences/Preferences.kt index 1598b2ca1..ff7390472 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/preferences/Preferences.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/preferences/Preferences.kt @@ -18,11 +18,17 @@ */ package org.isoron.uhabits.core.preferences +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import org.isoron.platform.time.DayOfWeek import org.isoron.platform.utils.StringUtils.Companion.joinLongs import org.isoron.platform.utils.StringUtils.Companion.splitLongs +import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.Timestamp +import org.isoron.uhabits.core.ui.NotificationTray import org.isoron.uhabits.core.ui.ThemeSwitcher import org.isoron.uhabits.core.utils.DateUtils.Companion.getFirstWeekdayNumberAccordingToLocale import java.util.LinkedList @@ -135,6 +141,23 @@ open class Preferences(private val storage: Storage) { storage.putBoolean("pref_short_toggle", enabled) } + internal fun setActiveNotifications(activeNotifications: Map) { + val activeById = activeNotifications.mapKeys { it.key.id } + val serialized = Json.encodeToString(activeById) + storage.putString("pref_active_notifications", serialized) + } + + internal fun getActiveNotifications(habitList: HabitList): HashMap { + val serialized = storage.getString("pref_active_notifications", "") + return if (serialized == "") { + HashMap() + } else { + val activeById = Json.decodeFromString(MapSerializer(Long.serializer(), NotificationTray.NotificationData.serializer()), serialized) + val activeByHabit = activeById.mapNotNull { (id, v) -> habitList.getById(id)?.let { it to v } } + activeByHabit.toMap(HashMap()) + } + } + fun removeListener(listener: Listener) { listeners.remove(listener) } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/NotificationTray.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/NotificationTray.kt index b2f085d34..f4439fe2f 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/NotificationTray.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/NotificationTray.kt @@ -18,17 +18,18 @@ */ package org.isoron.uhabits.core.ui +import kotlinx.serialization.Serializable import org.isoron.uhabits.core.AppScope import org.isoron.uhabits.core.commands.Command import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CreateRepetitionCommand import org.isoron.uhabits.core.commands.DeleteHabitsCommand import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.tasks.Task import org.isoron.uhabits.core.tasks.TaskRunner -import java.util.HashMap import java.util.Locale import java.util.Objects import javax.inject.Inject @@ -38,9 +39,31 @@ class NotificationTray @Inject constructor( private val taskRunner: TaskRunner, private val commandRunner: CommandRunner, private val preferences: Preferences, - private val systemTray: SystemTray + private val systemTray: SystemTray, + private val habitList: HabitList ) : CommandRunner.Listener, Preferences.Listener { - private val active: HashMap = HashMap() + + /** + * A mapping from habits to active notifications, automatically persisting on removal. + */ + private val active = object { + private val m: HashMap = + preferences.getActiveNotifications(habitList) + + val entries get() = m.entries + + operator fun set(habit: Habit, notificationData: NotificationData) { + m[habit] = notificationData + persist() + } + + fun remove(habit: Habit) { + m.remove(habit)?.let { persist() } // persist if changed + } + + fun persist() = preferences.setActiveNotifications(m) + } + fun cancel(habit: Habit) { val notificationId = getNotificationId(habit) systemTray.removeNotification(notificationId) @@ -64,7 +87,6 @@ class NotificationTray @Inject constructor( fun show(habit: Habit, timestamp: Timestamp, reminderTime: Long) { val data = NotificationData(timestamp, reminderTime) - active[habit] = data taskRunner.execute(ShowNotificationTask(habit, data)) } @@ -83,7 +105,7 @@ class NotificationTray @Inject constructor( return (id % Int.MAX_VALUE).toInt() } - private fun reshowAll() { + fun reshowAll() { for ((habit, data) in active.entries) { taskRunner.execute(ShowNotificationTask(habit, data)) } @@ -101,6 +123,7 @@ class NotificationTray @Inject constructor( fun log(msg: String) } + @Serializable internal class NotificationData(val timestamp: Timestamp, val reminderTime: Long) private inner class ShowNotificationTask(private val habit: Habit, data: NotificationData) : Task { @@ -164,6 +187,17 @@ class NotificationTray @Inject constructor( timestamp, reminderTime ) + if (data.shown) { + systemTray.log( + String.format( + Locale.US, + "Showing notification for habit %d silently because it has been shown before.", + habit.id + ) + ) + } + data.shown = true + active[habit] = data } private fun shouldShowReminderToday(): Boolean {