package com.tt.honeyDue.widget import android.content.Context import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OutOfQuotaPolicy import androidx.work.WorkManager import kotlinx.datetime.Clock import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime import java.util.concurrent.TimeUnit /** * Scheduler for the widget-refresh background work. Thin wrapper over * [WorkManager] that enqueues a [WidgetRefreshWorker] with the cadence * defined by [WidgetRefreshSchedule]. * * We use a chained one-time-work pattern rather than `PeriodicWorkRequest` * because: * - `PeriodicWorkRequest` has a 15-minute floor which is fine, but more * importantly can't *vary* its cadence between runs. * - The iOS-parity spec needs 30-min during the day and 120-min overnight * — so each run computes the next interval based on the local clock * and enqueues the next one-time request. * * On [schedulePeriodic], the worker is enqueued with an initial delay of * `intervalMinutes(now)`. On successful completion [WidgetRefreshWorker] * calls [schedulePeriodic] again to chain the next wake. */ object WidgetUpdateManager { /** Unique name for the periodic (chained) refresh queue. */ const val UNIQUE_WORK_NAME: String = "widget_refresh_periodic" /** Unique name for user- / app-triggered forced refreshes. */ const val FORCE_REFRESH_WORK_NAME: String = "widget_refresh_force" /** * Schedule the next periodic refresh. Delay = [WidgetRefreshSchedule.intervalMinutes] * evaluated against the current local-zone clock. Existing work under * [UNIQUE_WORK_NAME] is replaced — the new interval always wins. */ fun schedulePeriodic(context: Context) { val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) val delayMinutes = WidgetRefreshSchedule.intervalMinutes(now) val request = OneTimeWorkRequestBuilder() .setInitialDelay(delayMinutes, TimeUnit.MINUTES) .addTag(TAG) .build() WorkManager.getInstance(context.applicationContext) .enqueueUniqueWork(UNIQUE_WORK_NAME, ExistingWorkPolicy.REPLACE, request) } /** * Force an immediate refresh. Runs as an expedited worker so the OS * treats it as a foreground-ish job (best-effort — may be denied * quota, in which case it falls back to a regular one-time enqueue). */ fun forceRefresh(context: Context) { val request = OneTimeWorkRequestBuilder() .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .addTag(TAG) .build() WorkManager.getInstance(context.applicationContext) .enqueueUniqueWork(FORCE_REFRESH_WORK_NAME, ExistingWorkPolicy.REPLACE, request) } /** * Cancel any pending/chained periodic refresh. Does not affect * in-flight forced refreshes — call [cancel] from a logout flow to * stop the scheduler wholesale, or clear both queues explicitly. */ fun cancel(context: Context) { val wm = WorkManager.getInstance(context.applicationContext) wm.cancelUniqueWork(UNIQUE_WORK_NAME) wm.cancelUniqueWork(FORCE_REFRESH_WORK_NAME) } private const val TAG = "widget_refresh" }