Files
honeyDueKMP/composeApp/src/androidMain/kotlin/com/tt/honeyDue/widget/WidgetRefreshSchedule.kt
T
Trey T dbff329384 P3 Stream L: widget refresh scheduler (WorkManager, iOS cadence)
WidgetRefreshSchedule: 30-min day / 120-min overnight (6am–11pm split).
WidgetRefreshWorker: CoroutineWorker fetches via APILayer -> repo -> widget.update.
WidgetUpdateManager: chained one-time enqueue pattern (WorkManager PeriodicWork
can't vary cadence).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 12:54:35 -05:00

63 lines
2.3 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.tt.honeyDue.widget
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.plus
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
/**
* Pure-logic schedule for widget refresh cadence. Mirrors the iOS-parity
* split from the P3 parity plan:
*
* - 06:00 (inclusive) .. 23:00 (exclusive) local → refresh every 30 minutes
* - 23:00 (inclusive) .. 06:00 (exclusive) local → refresh every 120 minutes
*
* iOS ([BackgroundTaskManager.swift]) uses a random 12am4am overnight
* BGAppRefreshTask window rather than a fixed cadence, because iOS
* `BGTaskScheduler` is coalesced by the system. Android's WorkManager runs
* user-defined intervals, so this file encodes the ios-parity cadence the
* plan specifies. The split 30/120 preserves the core intent: frequent
* while awake, sparse while the user is asleep.
*/
object WidgetRefreshSchedule {
private const val DAY_START_HOUR_INCLUSIVE = 6 // 06:00 local
private const val DAY_END_HOUR_EXCLUSIVE = 23 // 23:00 local
const val DAY_INTERVAL_MINUTES: Long = 30L
const val NIGHT_INTERVAL_MINUTES: Long = 120L
/**
* Returns the refresh interval (in minutes) for a wall-clock time.
*
* Hour bands:
* - [06:00, 23:00) → [DAY_INTERVAL_MINUTES] (30)
* - [23:00, 06:00) → [NIGHT_INTERVAL_MINUTES] (120)
*/
fun intervalMinutes(at: LocalDateTime): Long {
val hour = at.hour
return if (hour in DAY_START_HOUR_INCLUSIVE until DAY_END_HOUR_EXCLUSIVE) {
DAY_INTERVAL_MINUTES
} else {
NIGHT_INTERVAL_MINUTES
}
}
/**
* Returns `now + intervalMinutes(now)` as a [LocalDateTime].
*
* Arithmetic is performed through [TimeZone.UTC] to avoid ambiguity
* around DST transitions in the local zone — the absolute minute offset
* is what WorkManager's `setInitialDelay` consumes, so the returned
* wall-clock value is for display/testing only.
*/
fun nextRefreshTime(now: LocalDateTime): LocalDateTime {
val interval = intervalMinutes(now)
val instant = now.toInstant(TimeZone.UTC)
val next = instant.plus(interval, DateTimeUnit.MINUTE)
return next.toLocalDateTime(TimeZone.UTC)
}
}