summaryrefslogtreecommitdiff
path: root/docs/superpowers/specs/2026-07-12-widget-refresh-button-design.md
blob: 4a3ad34605d85994d78c0303de28048f06fc9fc0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# Widget Refresh Button — Design

## Context

First of five widget feature requests (refresh button, quick add, clickable-date reschedule, recurrence in the popup, overdue badge), being designed and shipped as separate small cycles rather than one batch. This spec covers only the refresh button.

`RefreshWorker` already exists and does the right thing on completion: `fetchAndPersist()` then `DootWidget().updateAll()`. Today it only runs on a 15-minute periodic schedule (`RefreshWorker.schedule()`) or via `RefreshWorker.runOnce()`, which nothing currently calls. There is no manual trigger in the widget UI.

## Goal

A tappable refresh icon on the widget that triggers an immediate fetch, with visual feedback while the fetch is in flight.

## Constraint

Android AppWidgets render via `RemoteViews`, which does not support arbitrary View animation (no rotating-icon spinner). The realistic version of "spinner" is a static icon swap: refresh glyph ↔ a distinct "loading" glyph, swapped synchronously on tap and swapped back when the worker finishes. Confirmed acceptable with the user.

## Design

**Flow:**
1. User taps the refresh icon.
2. `RefreshTaskAction.onAction()` (new `ActionCallback`, mirrors the existing `CompleteTaskAction`) sets `Keys.IS_REFRESHING = true` in the widget's DataStore and calls `DootWidget().updateAll(context)` immediately — the icon flips to the loading glyph before any network call happens.
3. The same action enqueues `RefreshWorker.runOnce(context)`.
4. `RefreshWorker.doWork()` is extended so that, on **both** the success and failure branches, it clears `Keys.IS_REFRESHING` and calls `DootWidget().updateAll(context)` again — the icon always reverts, even if the fetch errors out and `Result.retry()` is returned (a retry is still "not actively refreshing" from the user's point of view between attempts).

**Components:**

- `data/DataStore.kt` — add `val IS_REFRESHING = booleanPreferencesKey("is_refreshing")` to `Keys`.
- `res/drawable/ic_refresh.xml` — new 24dp vector icon, same style/stroke convention as the existing `ic_checkbox_empty.xml`.
- `res/drawable/ic_refresh_loading.xml` — new 24dp vector icon, visually distinct (e.g. hourglass or three dots) from the idle refresh icon.
- `widget/ui/Actions.kt` — new `RefreshTaskAction : ActionCallback`, no parameters needed.
- `widget/ui/DootWidget.kt`:
  - `provideGlance()` reads `prefs[Keys.IS_REFRESHING] ?: false` and passes it into `WidgetRoot(items, now, isRefreshing)`.
  - `WidgetRoot`'s existing "TODAY" header `Row` gains a second child: a new `RefreshButton(isRefreshing: Boolean)` composable, right-aligned (the `Row` needs `horizontalArrangement`/a spacer or `defaultWeight()` on the "TODAY" text so the icon lands on the right edge).
  - `RefreshButton` renders `ic_refresh_loading` if `isRefreshing`, else `ic_refresh`; both wrapped in a `Box` with `clickable(actionRunCallback<RefreshTaskAction>())`, sized to match the existing checkbox tap target (24dp box, matching `TaskRow`'s checkbox pattern).
- `widget/work/RefreshWorker.kt` — `doWork()`'s `fold` branches both gain the clear-flag-and-update step before returning their `Result`.

**Error handling:** No new error states. `RefreshWorker.doWork()` already returns `Result.retry()` on failure; WorkManager's existing backoff handles the retry timing. The icon simply reverts to idle between attempts rather than staying in a stuck loading state.

**Testing:** No new unit-test surface — this is Glance/RemoteViews composition and WorkManager wiring, consistent with how the rest of `DootWidget.kt` and the `*Worker.kt` classes are (not) unit tested today. Verified by building the APK, installing on device, and confirming the tap → loading-icon → fetch → idle-icon cycle live, the same verification approach used for every other widget fix this session.

## Out of scope

The other four widget features (quick add, clickable-date reschedule, recurrence display, overdue badge) — each gets its own spec.