voo_watch_ui 0.4.0
voo_watch_ui: ^0.4.0 copied to clipboard
Cross-platform UI DSL for smartwatches. Define your watch UI in Dart and render it identically on Apple Watch (native SwiftUI) and Wear OS (Flutter).
Changelog #
All notable changes to voo_watch_ui will be documented in this file.
0.4.0 - 2026-04-29 #
Added #
- Closure-form event handlers.
WatchView.button(onTap: () => count++),WatchView.toggle(onChanged: (bool v) => ...), etc. — every interactive factory now accepts a Dart closure in addition to the existing string/WatchEventIdforms. The closure is auto-registered against a synthetic id, replaced on eachrender()call, and dispatched when the watch fires the event back. Eliminates mostVooWatchUi.instance.onEvent(...)registrations in the common case. Supported signatures:VoidCallback,void Function(bool|int|double|String),void Function(Map<String, Object?>).WatchAlertAction.onTapaccepts the same forms (constructor is no longerconst— drop theconstkeyword on call sites if present). - Typed icons. New
WatchIconDatavalue type pairs an SF Symbol name with a Material code-point so the same call renders correctly on both Apple Watch and Wear OS / phone-side preview. CuratedWatchIconscatalog of ~40 common icons.WatchView.icon/chip/iconButton/toastfactories now acceptObjecticon —WatchIconData, Flutter'sIconData(auto-resolves SF Symbol via the catalog, falling back toquestionmark.circlefor uncatalogued icons), or a rawString(legacy SF Symbol passthrough). WatchEventId— typed const wrapper around an event id string. PairWatchView.button(onTap: AppEvents.refresh)withVooWatchUi.instance.onEvent(AppEvents.refresh, ...)to share one source of truth and let the IDE catch typos via autocomplete / rename / find-usages.WatchUiBootstrap— single wrapper widget that nestsWatchUiHotRestartBinding→WatchUiHotReloadBinding→WatchUiThemeBindingin the correct order. Per-layer flags (hotReload,hotRestart,themeSync) for opt-out. Replaces the three-deep nested form in yourMaterialApp.home.package:voo_watch_ui/testing.dart— exportsinstallFakeVooWatch(),resetVooWatch(), andwithFakeVooWatch(() async { ... })so tests no longer have to repeat theVooWatch.debugReset/FakeVooWatchTransport/VooWatch.debugInstallboilerplate by hand.VooWatchUi.dispatchEvent(eventId, payload)andhasHandlerFor(eventId)— public hooksWatchUiPreviewuses to route closure-form events through the active callback registry, so preview clicks fire the same handlers a real watch tap would.
Changed #
voo_watch_ui:initnow also wires the watch target's@mainApp.swift(preserves the Xcode-generated struct name; conservative about user-modified files — pass--forceto overwrite). Setup goes from 5 manual steps to 4 — the README's "Replace App.swift" step is gone.- Demo (
apps/voo_watch_demo) migrated to closure-form events on the counter and reset/end-run alert flows; usesWatchIcons.heart/fire/WatchIconDatafor cross-platform icons. Most other events stay on the string-basedonEventAPI to demonstrate backwards compatibility. WatchAlertActionconstructor is no longerconst(it accepts the closure/WatchEventId/StringonTapforms). String-only call sites still compile — dropconstfrom list literals if present.
0.3.1 - 2026-04-28 #
Added #
WatchUiHotReloadBinding— drop-in widget that re-publishes the last watch tree onState.reassemble()so phone-side hot reload pushes edited trees to the paired device even whenrender(...)is called frominitStateor an event handler (which don't re-fire on hot reload). Toggle off withenabled: false. Pairs with the existingWatchUiThemeBinding— wrap your home screen in both:WatchUiHotReloadBinding(child: WatchUiThemeBinding(child: HomeScreen())).VooWatchUi.republishOnHotReload()— public API powering the binding above. Clears the dedup cache and re-flushes the most recent tree.
Fixed #
- Watch theme is now correct on cold start when the phone app hasn't been
launched in the current session. The Wear OS Flutter watch
(
WatchUiTreeListener) now caches the most recently seen theme toSharedPreferencesand hydrates from it on init; the Apple Watch SwiftUI watch (WatchUiState) caches toUserDefaultsand hydrates in its initializer. Previously the watch would flash indigodefaultDarkuntil the phone app'sWatchUiThemeBindingpublished its first frame.
Changed #
VooWatchUi.rendernow also publishes viaupdateApplicationContextwhenever the theme changes (typically once per app launch and again on dark-mode toggle). This seeds Apple Watch'sWCSession.receivedApplicationContextso a watch launched standalone — phone backgrounded or never opened — hydrates the correct brand theme on cold start. Tree-only renders stay on the livesendMessagechannel as before. Effective extra IPC cost is roughly one applicationContext write per app launch.WatchUiTreeListener.initialThemedoc clarified: it's the floor used only when both the disk cache and the live channel are empty. Pass aWatchTheme.fromColorScheme(myBrandScheme)so users who install the watch app and open it before ever launching the phone app see brand colors instead ofdefaultDark.
Migration #
- The watchOS
WatchUiState.swifttemplate emitted byvoo_watch_ui:initgained a custom initializer forUserDefaultshydration. Re-rundart run voo_watch_ui:initin your watch target, or hand-merge theprivate init()andthemeCacheKeyadditions from the published template.
0.3.0 - 2026-04-28 #
Added #
WatchUiPreviewwidget — a drop-in watch-shaped frame aroundWatchUiFlutterRendererfor hot-reload previews on phone, desktop, or web. Supports Apple Watch and Wear OS round silhouettes viaWatchPreviewShape. Replaces the ~25-line ad-hoc preview block that was duplicated across the example and demo apps.- Hot-reload preview workflow documentation in the README — run
apps/voo_watch_previewwithflutter run -d chromefor a sub-second iteration loop on watch UI trees without an iOS Watch target or paired device.
Changed #
voo_watch_uinow works on Flutter web. The transitivevoo_watchbridge automatically falls back to an in-memory transport whenkIsWebis true, soVooWatchUi.instance.render(...)is a harmless no-op in browser-based previews.- Bumped
voo_watchconstraint to^0.3.0.
0.2.2 - 2026-04-28 #
Added #
example/app — phone-side demo that builds aWatchViewtree, previews it on the phone with the sameWatchUiFlutterRendererthat ships on Wear OS, registersonEventhandlers for tap/toggle/slider, and exposes buttons to trigger watch haptics. Required for pub.dev's "Provide documentation" category.
Changed #
- Declared
platforms: ios, androidexplicitly inpubspec.yaml(previously inferred via thevoo_watchimport chain). - Inlined
formatter: page_width: 160inanalysis_options.yamlso pana scores the package under the project's column width.
0.2.1 - 2026-04-27 #
Documentation #
- Expanded the Apple Watch setup section into a numbered five-step walk-
through (add target → scaffold templates → replace
App.swift→ fix the Xcode build cycle → run), including the--watch-targetand--forceflags and a table describing what each scaffolded Swift file does. - New Troubleshooting section covering the most common pitfalls hit on
first run: Xcode's "unable to attach DB" build database corruption, the
"NSPOSIXErrorDomain code 2" install error (with the IDE/CLI DerivedData
hash-mismatch root cause), the "Awaiting UI…" stall, theme misplacement
under
MaterialApp, simulator animation lag, and Wear OS pairing requirements.
0.2.0 - 2026-04-27 #
Added #
- Theme system.
WatchTheme(9 semantic colors: primary, onPrimary, surface, onSurface, muted, background, success, warning, danger),WatchTheme.defaultDark,WatchTheme.indigo,WatchTheme.fromColorScheme, and JSON roundtrip.WatchUiUpdatenow carries an optional theme that the watch caches; updates that don't change the theme omit it. WatchUiThemeBinding. AStatelessWidgetthat readsTheme.of(context).colorSchemeand pushes a matchingWatchThemetoVooWatchUi.instance.theme. Drop it underMaterialAppand the watch follows the phone's brand colors automatically — including dark-mode toggles.- Modals.
WatchAlert(blocking confirmation with cancel/destructive actions),WatchSheet(modal bottom sheet rendering arbitrary content),WatchToast(transient overlay banner with optional icon/color/position). All declarative — phone owns visibility. - Animation primitives.
WatchAnimatedScaleandWatchAnimatedRotationfor state-tracking animation.WatchPulsefor trigger-based animation that runs locally on the watch (no IPC round-trip per frame — what you want for tap feedback and success bursts). - Haptics.
WatchHapticKindenum (click / success / warning / failure / notification / start / stop) andVooWatchUi.instance.haptic(kind)API. Maps toWKInterfaceDevice.play()on watchOS and Flutter'sHapticFeedbackon Wear OS. WatchSafeArea. Per-edge safe-area handling for the watch's status bar and TabView page-dot insets.- Display widgets.
WatchAvatar(circular image + initials fallback),WatchBadge(count or dot overlay; auto "99+" cap),WatchChip(pill-shaped tag with optional icon). - Input widget.
WatchStepper(+/- value editor; nativeStepperon watchOS picks up digital crown rotation). - Per-instance color overrides on every input primitive: Switch, Slider, Checkbox, Button, IconButton, TextField, Stepper. Themes provide the default; per-widget props override.
- Progress polish.
WatchProgressIndicatornow acceptscolor,size, andstrokeWidth. WatchUiTreeListener.initialTheme. Wear OS apps can now set a startup theme so the watch shows brand colors from frame 1, before the phone connects.
Changed #
- The Flutter renderer is now theme-aware:
Card,Text,Button,Icon,Divider,IconButton,TextField,Switch,Slider,Checkbox,Progress,Stepper, and the page-dot indicator all read defaults from the current theme. - The SwiftUI renderer mirrors the same theme-aware defaults via
@Environment(\.watchUiTheme). _PageView(SwiftUI) now pins each page toWKInterfaceDevice.screenBoundsto escape watchOS TabView's design-imposed margins. List titles no longer truncate to "…"._Button(SwiftUI) now usesButton { } label: { WatchUiButtonLabel }with.buttonStyle(.plain)so an outerExpandedactually fills the row.- Optimistic-UI pattern for live inputs (Slider, Toggle, Checkbox, Stepper,
TextField):
@Statemirrors the prop and resyncs viaonChange(of:)— prevents the IPC round-trip lag on each tap.
Fixed #
_LiveSliderno longer ping-pongs between the phone-driven and local values (loose-equality syncback guard).- Cards span the full watch width (the long-running TabView inset bug).
WatchAnimatedOpacityis no longer the only motion primitive.
0.1.0 - 2026-04-26 #
Added #
- Initial release of the cross-platform Dart watch UI DSL.
- 30 widget primitives covering layout (Column, Row, Stack, Wrap, Center,
Padding, SizedBox, Expanded, Flexible, Spacer), display (Text, Image, Icon,
Container, Divider, Card), input (Button, IconButton, TextField, Switch,
Slider, Checkbox), lists (ListView, ScrollView), motion (GestureDetector,
AnimatedOpacity), feedback (ProgressIndicator), navigation (Page, PageView),
and a
Customescape hatch. VooWatchUifacade withrender(), event registry, and tap callbacks dispatched through thevoo_watchbridge.- Pure-Dart Flutter renderer (used on Wear OS and in tests).
- SwiftUI renderer template set in
tools/watchos_renderer/(copied into a watchOS target bydart run voo_watch_ui:init). WatchUiUpdateandWatchUiEventmessage types built onvoo_watch.FakeWatchUitest harness exported viapackage:voo_watch_ui/testing.dart.