From 800fd7e336da869c0bae6da40cce15dbcccea30a Mon Sep 17 00:00:00 2001 From: kchinnasamy Date: Mon, 11 May 2026 20:11:25 -0700 Subject: [PATCH] feat: add TimelineReminder support to TimelinePin Adds a reminders field to TimelinePin so Cobble/Core can insert the corresponding BlobDatabase.Reminder entries that trigger the watch buzz. Without reminders, timeline pins are silent. --- .../pebblekit2/common/model/TimelinePin.kt | 22 ++++++++ .../common/model/TimelinePinSerialization.kt | 56 ++++++++++++++----- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/common-api/src/main/kotlin/io/rebble/pebblekit2/common/model/TimelinePin.kt b/common-api/src/main/kotlin/io/rebble/pebblekit2/common/model/TimelinePin.kt index e3d12a2..d00ce92 100644 --- a/common-api/src/main/kotlin/io/rebble/pebblekit2/common/model/TimelinePin.kt +++ b/common-api/src/main/kotlin/io/rebble/pebblekit2/common/model/TimelinePin.kt @@ -25,6 +25,28 @@ public data class TimelinePin( * Description of the values to populate the layout when the user views the pin. */ val layout: TimelineLayout, + + /** + * Optional reminders that buzz the watch before this pin's start time. + * Each reminder is inserted into BlobDatabase.Reminder and linked to this pin. + */ + val reminders: List = emptyList(), +) { + public companion object +} + +/** + * A reminder that fires before a [TimelinePin] and buzzes the watch. + * + * Use [TimelineLayoutType.GENERIC_REMINDER] for the layout type, and + * `system://images/NOTIFICATION_REMINDER` as the icon. + * + * @param time absolute time at which the reminder fires (e.g. pin.startTime - 15.minutes) + * @param layout layout shown on the watch when the reminder fires + */ +public data class TimelineReminder( + val time: Instant, + val layout: TimelineLayout, ) { public companion object } diff --git a/common/src/main/kotlin/io/rebble/pebblekit2/common/model/TimelinePinSerialization.kt b/common/src/main/kotlin/io/rebble/pebblekit2/common/model/TimelinePinSerialization.kt index 6a977db..d12f54f 100644 --- a/common/src/main/kotlin/io/rebble/pebblekit2/common/model/TimelinePinSerialization.kt +++ b/common/src/main/kotlin/io/rebble/pebblekit2/common/model/TimelinePinSerialization.kt @@ -6,11 +6,23 @@ import kotlin.time.Duration import kotlin.time.Instant public fun TimelinePin.Companion.fromBundle(bundle: Bundle): TimelinePin { + val reminderCount = bundle.getInt(KEY_REMINDERS_COUNT, 0) + val reminders = (0 until reminderCount).map { i -> + TimelineReminder.fromBundle(bundle.getBundle("$KEY_REMINDER_PREFIX$i") ?: Bundle()) + } return TimelinePin( id = bundle.getString(KEY_ID) ?: error("missing id"), startTime = Instant.parse(bundle.getString(KEY_START_TIME) ?: error("missing start time")), duration = bundle.getString(KEY_DURATION)?.let { Duration.parse(it) }, layout = TimelineLayout.fromBundle(bundle), + reminders = reminders, + ) +} + +public fun TimelineReminder.Companion.fromBundle(bundle: Bundle): TimelineReminder { + return TimelineReminder( + time = Instant.parse(bundle.getString(KEY_REMINDER_TIME) ?: error("missing reminder time")), + layout = TimelineLayout.fromBundle(bundle), ) } @@ -20,22 +32,37 @@ public fun TimelinePin.toBundle(): Bundle { putString(KEY_ID, id) putString(KEY_START_TIME, startTime.toString()) duration?.let { putString(KEY_DURATION, it.toIsoString()) } - putString(KEY_LAYOUT_TYPE, layout.type.code) - layout.title?.let { putString(KEY_LAYOUT_TITLE, it) } - layout.subtitle?.let { putString(KEY_LAYOUT_SUBTITLE, it) } - layout.body?.let { putString(KEY_LAYOUT_BODY, it) } - layout.tinyIcon?.let { putString(KEY_LAYOUT_TINY_ICON, it) } - layout.smallIcon?.let { putString(KEY_LAYOUT_SMALL_ICON, it) } - layout.largeIcon?.let { putString(KEY_LAYOUT_LARGE_ICON, it) } - layout.primaryColor?.let { putString(KEY_LAYOUT_PRIMARY_COLOR, it) } - layout.secondaryColor?.let { putString(KEY_LAYOUT_SECONDARY_COLOR, it) } - layout.backgroundColor?.let { putString(KEY_LAYOUT_BACKGROUND_COLOR, it) } - layout.headings?.let { putStringArray(KEY_LAYOUT_HEADINGS, it.toTypedArray()) } - layout.paragraphs?.let { putStringArray(KEY_LAYOUT_PARAGRAPHS, it.toTypedArray()) } - layout.lastUpdated?.let { putString(KEY_LAYOUT_LAST_UPDATED, it.toString()) } + putLayoutFields(layout) + putInt(KEY_REMINDERS_COUNT, reminders.size) + reminders.forEachIndexed { i, reminder -> + putBundle("$KEY_REMINDER_PREFIX$i", reminder.toBundle()) + } } } +public fun TimelineReminder.toBundle(): Bundle { + return Bundle().apply { + putString(KEY_REMINDER_TIME, time.toString()) + putLayoutFields(layout) + } +} + +private fun Bundle.putLayoutFields(layout: TimelineLayout) { + putString(KEY_LAYOUT_TYPE, layout.type.code) + layout.title?.let { putString(KEY_LAYOUT_TITLE, it) } + layout.subtitle?.let { putString(KEY_LAYOUT_SUBTITLE, it) } + layout.body?.let { putString(KEY_LAYOUT_BODY, it) } + layout.tinyIcon?.let { putString(KEY_LAYOUT_TINY_ICON, it) } + layout.smallIcon?.let { putString(KEY_LAYOUT_SMALL_ICON, it) } + layout.largeIcon?.let { putString(KEY_LAYOUT_LARGE_ICON, it) } + layout.primaryColor?.let { putString(KEY_LAYOUT_PRIMARY_COLOR, it) } + layout.secondaryColor?.let { putString(KEY_LAYOUT_SECONDARY_COLOR, it) } + layout.backgroundColor?.let { putString(KEY_LAYOUT_BACKGROUND_COLOR, it) } + layout.headings?.let { putStringArray(KEY_LAYOUT_HEADINGS, it.toTypedArray()) } + layout.paragraphs?.let { putStringArray(KEY_LAYOUT_PARAGRAPHS, it.toTypedArray()) } + layout.lastUpdated?.let { putString(KEY_LAYOUT_LAST_UPDATED, it.toString()) } +} + private fun TimelineLayout.Companion.fromBundle(bundle: Bundle): TimelineLayout { val typeCode = bundle.getString(KEY_LAYOUT_TYPE) ?: "" val type = TimelineLayoutType.entries.firstOrNull { it.code == typeCode } @@ -77,3 +104,6 @@ private const val KEY_LAYOUT_BACKGROUND_COLOR = "LAYOUT_BACKGROUND_COLOR" private const val KEY_LAYOUT_HEADINGS = "LAYOUT_HEADINGS" private const val KEY_LAYOUT_PARAGRAPHS = "LAYOUT_PARAGRAPHS" private const val KEY_LAYOUT_LAST_UPDATED = "LAYOUT_LAST_UPDATED" +private const val KEY_REMINDERS_COUNT = "REMINDERS_COUNT" +private const val KEY_REMINDER_PREFIX = "REMINDER_" +private const val KEY_REMINDER_TIME = "REMINDER_TIME"