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.
pull/1509/head
Felix Wiemuth 3 years ago
parent 794289b9b8
commit 09c46f5b88

@ -94,6 +94,7 @@ class HabitsApplication : Application() {
taskRunner.execute { taskRunner.execute {
reminderScheduler.scheduleAll() reminderScheduler.scheduleAll()
widgetUpdater.updateWidgets() widgetUpdater.updateWidgets()
notificationTray.reshowAll()
} }
} }

@ -72,9 +72,10 @@ class HabitsModule(dbFile: File) {
taskRunner: TaskRunner, taskRunner: TaskRunner,
commandRunner: CommandRunner, commandRunner: CommandRunner,
preferences: Preferences, preferences: Preferences,
screen: AndroidNotificationTray screen: AndroidNotificationTray,
habitList: HabitList
): NotificationTray { ): NotificationTray {
return NotificationTray(taskRunner, commandRunner, preferences, screen) return NotificationTray(taskRunner, commandRunner, preferences, screen, habitList)
} }
@Provides @Provides

@ -18,6 +18,7 @@
*/ */
package org.isoron.uhabits.core.models package org.isoron.uhabits.core.models
import kotlinx.serialization.Serializable
import org.isoron.platform.time.LocalDate import org.isoron.platform.time.LocalDate
import org.isoron.uhabits.core.utils.DateFormats.Companion.getCSVDateFormat import org.isoron.uhabits.core.utils.DateFormats.Companion.getCSVDateFormat
import org.isoron.uhabits.core.utils.DateFormats.Companion.getDialogDateFormat import org.isoron.uhabits.core.utils.DateFormats.Companion.getDialogDateFormat
@ -29,6 +30,7 @@ import java.util.Date
import java.util.GregorianCalendar import java.util.GregorianCalendar
import java.util.TimeZone import java.util.TimeZone
@Serializable
data class Timestamp(var unixTime: Long) : Comparable<Timestamp> { data class Timestamp(var unixTime: Long) : Comparable<Timestamp> {
constructor(cal: GregorianCalendar) : this(cal.timeInMillis) constructor(cal: GregorianCalendar) : this(cal.timeInMillis)

@ -18,11 +18,17 @@
*/ */
package org.isoron.uhabits.core.preferences 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.time.DayOfWeek
import org.isoron.platform.utils.StringUtils.Companion.joinLongs import org.isoron.platform.utils.StringUtils.Companion.joinLongs
import org.isoron.platform.utils.StringUtils.Companion.splitLongs 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.HabitList
import org.isoron.uhabits.core.models.Timestamp 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.ui.ThemeSwitcher
import org.isoron.uhabits.core.utils.DateUtils.Companion.getFirstWeekdayNumberAccordingToLocale import org.isoron.uhabits.core.utils.DateUtils.Companion.getFirstWeekdayNumberAccordingToLocale
import java.util.LinkedList import java.util.LinkedList
@ -135,6 +141,23 @@ open class Preferences(private val storage: Storage) {
storage.putBoolean("pref_short_toggle", enabled) storage.putBoolean("pref_short_toggle", enabled)
} }
internal fun setActiveNotifications(activeNotifications: Map<Habit, NotificationTray.NotificationData>) {
val activeById = activeNotifications.mapKeys { it.key.id }
val serialized = Json.encodeToString(activeById)
storage.putString("pref_active_notifications", serialized)
}
internal fun getActiveNotifications(habitList: HabitList): HashMap<Habit, NotificationTray.NotificationData> {
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) { fun removeListener(listener: Listener) {
listeners.remove(listener) listeners.remove(listener)
} }

@ -18,17 +18,18 @@
*/ */
package org.isoron.uhabits.core.ui package org.isoron.uhabits.core.ui
import kotlinx.serialization.Serializable
import org.isoron.uhabits.core.AppScope import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.commands.Command import org.isoron.uhabits.core.commands.Command
import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateRepetitionCommand import org.isoron.uhabits.core.commands.CreateRepetitionCommand
import org.isoron.uhabits.core.commands.DeleteHabitsCommand import org.isoron.uhabits.core.commands.DeleteHabitsCommand
import org.isoron.uhabits.core.models.Habit 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.models.Timestamp
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.tasks.Task import org.isoron.uhabits.core.tasks.Task
import org.isoron.uhabits.core.tasks.TaskRunner import org.isoron.uhabits.core.tasks.TaskRunner
import java.util.HashMap
import java.util.Locale import java.util.Locale
import java.util.Objects import java.util.Objects
import javax.inject.Inject import javax.inject.Inject
@ -38,9 +39,31 @@ class NotificationTray @Inject constructor(
private val taskRunner: TaskRunner, private val taskRunner: TaskRunner,
private val commandRunner: CommandRunner, private val commandRunner: CommandRunner,
private val preferences: Preferences, private val preferences: Preferences,
private val systemTray: SystemTray private val systemTray: SystemTray,
private val habitList: HabitList
) : CommandRunner.Listener, Preferences.Listener { ) : CommandRunner.Listener, Preferences.Listener {
private val active: HashMap<Habit, NotificationData> = HashMap()
/**
* A mapping from habits to active notifications, automatically persisting on removal.
*/
private val active = object {
private val m: HashMap<Habit, NotificationData> =
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) { fun cancel(habit: Habit) {
val notificationId = getNotificationId(habit) val notificationId = getNotificationId(habit)
systemTray.removeNotification(notificationId) systemTray.removeNotification(notificationId)
@ -64,7 +87,6 @@ class NotificationTray @Inject constructor(
fun show(habit: Habit, timestamp: Timestamp, reminderTime: Long) { fun show(habit: Habit, timestamp: Timestamp, reminderTime: Long) {
val data = NotificationData(timestamp, reminderTime) val data = NotificationData(timestamp, reminderTime)
active[habit] = data
taskRunner.execute(ShowNotificationTask(habit, data)) taskRunner.execute(ShowNotificationTask(habit, data))
} }
@ -83,7 +105,7 @@ class NotificationTray @Inject constructor(
return (id % Int.MAX_VALUE).toInt() return (id % Int.MAX_VALUE).toInt()
} }
private fun reshowAll() { fun reshowAll() {
for ((habit, data) in active.entries) { for ((habit, data) in active.entries) {
taskRunner.execute(ShowNotificationTask(habit, data)) taskRunner.execute(ShowNotificationTask(habit, data))
} }
@ -101,6 +123,7 @@ class NotificationTray @Inject constructor(
fun log(msg: String) fun log(msg: String)
} }
@Serializable
internal class NotificationData(val timestamp: Timestamp, val reminderTime: Long) internal class NotificationData(val timestamp: Timestamp, val reminderTime: Long)
private inner class ShowNotificationTask(private val habit: Habit, data: NotificationData) : private inner class ShowNotificationTask(private val habit: Habit, data: NotificationData) :
Task { Task {
@ -164,6 +187,17 @@ class NotificationTray @Inject constructor(
timestamp, timestamp,
reminderTime 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 { private fun shouldShowReminderToday(): Boolean {

Loading…
Cancel
Save