lazy_ally 1.0.3
lazy_ally: ^1.0.3 copied to clipboard
Make your Flutter app accessible with one widget: system-aware theming, IBM color-blind-safe support, reduce-motion, and screen-reader announcements, using stock Flutter only.
Changelog #
1.0.3 - 2026-07-27 #
- Small copy change in README.md
1.0.2 - 2026-07-27 #
- Small copy change in README.md
1.0.0 - 2026-07-27 #
Initial stable release. Core primitives for accessible theming in stock Flutter.
LazyAllywidget: wraps an app and provides theming without replacingMaterialApp.LazyAllyController(ChangeNotifier) andLazyAllyProvider(InheritedNotifier) handle stock-Flutter state propagation, withcontext.lazyAlly/context.lazyAllyThemeconvenience accessors. The controller is split acrosslazy_ally_controller_*.dartpart files by concern (brightness, color-blind, custom schemes/themes, text scale, reduce motion) for readability, but stays one class, one library.- Required
light/darkThemeData. Brightness modes are exactlykLazyAllySystem(the default, which tracks the OS's live platform brightness continuously, not just at first build),kLazyAllyLight, andkLazyAllyDark. There is no built-in "extra dark" or similar mode. Register a variant like that as a namedcustomThemesentry instead. LazyAllyColorSchemePair(({ColorScheme light, ColorScheme dark})) forcolorBlindSchemeandcustomColorSchemesentries swaps onlyThemeData.colorSchemeon top of whichever base theme is active.LazyAllyThemePair(({ThemeData light, ThemeData dark})) forcustomThemesentries replaces the entire base theme (typography, shapes, component themes). Both always resolve, live, to whichever member matches the active brightness. Supplying a complete, correct pair per brightness is the caller's responsibility.- Color-blind flag (
colorBlind) is orthogonal to brightness mode. When on,activeThemeswaps in a fullColorSchemerather than remapping individual colors. It defaults to a generated IBM color-blind-safe pair and is overridable throughcolorBlindScheme. customColorSchemes: Map<String, LazyAllyColorSchemePair>(plusinitialCustomScheme,setCustomScheme) andcustomThemes: Map<String, LazyAllyThemePair>(plusinitialCustomTheme,setCustomTheme) form two independent, open-ended named registries. A selected custom scheme overridescolorBlindatactiveThemeresolution. A selected custom theme replaceslight/darkas the base before that resolution happens.- Text scale has configurable
textScaleMin/textScaleMax/textScaleDefault, seeded from the OS's own text scaler on first build rather than overriding it outright. - Reduce-motion flag (
reduceMotion) is seeded fromMediaQuery.disableAnimationsOfand wired back intoLazyAlly's ownMediaQueryoverride. It is also exposed asreduceMotionListenable(ValueListenable<bool>), withLazyAllyReducedMotionBuildercovering the common case of swapping to a static-but-still-communicative widget. LazyAllyPreferencesis a serializable snapshot (mode,colorBlind,textScaleFactor,selectedCustomScheme,selectedCustomTheme,reduceMotion) withtoJson/fromJson.LazyAllyacceptsloadLazyAllyPreferences/onLazyAllyPreferencesChangedhooks (plain functions, no bundled storage dependency) so you can restore or persist it however you like. A plain (non-async)loadLazyAllyPreferences, for example one pre-fetched beforerunApp(), is applied ininitStatebefore the first frame, with no flash of constructor defaults. Anasyncone is applied once it resolves, which may show defaults for a frame or two.LazyAllyLabelsis a plain data class holding every string LazyAlly's widgets render or announce, English by default, overridable per-widget or once throughLazyAllyPanel(labels: ...). It needs noLocale/intldependency, so you bring your own localizations.lazyAllyAnnounce(SemanticsService.sendAnnouncement): every interactive LazyAlly widget announces its own state changes. It is exported so custom controls (for example, a bespokeitemBuilder) can match the same standard.- Atomic control widgets:
LazyAllyBrightnessSelector,LazyAllyTextScaleSlider,LazyAllyColorBlindSwitch,LazyAllyReduceMotionSwitch,LazyAllyCustomSchemeSelector, andLazyAllyCustomThemeSelector, plusLazyAllyPanel, a pre-composed layout of the first four (showReduceMotionhides that row). None of them opens, sizes, or manages a sheet/dialog/route. Navigation stays the calling app's job. Seeexample/for the intended pattern: aFloatingActionButtonthat opensshowModalBottomSheet(builder: (_) => const LazyAllyPanel()).LazyAllyPanelre-asserts a liveThemefromcontext.lazyAlly.activeTheme, sinceshowModalBottomSheet/showDialogcaptureThemeas a static snapshot at push time.LazyAllyBrightnessSelectornormally renders aWrapof icon-over-labelChoiceChips, not aSegmentedButton, which has nowhere to go but overflow with 3 options at large text scale.LazyAllyBrightnessSelector,LazyAllyTextScaleSlider, and the defaultChoiceChipitem inLazyAllyCustomSchemeSelector/LazyAllyCustomThemeSelectorall useLazyAllyReducedMotionBuilderto swap to a simpler, non-animated widget (plain toggle buttons, "-"/"+" step buttons) underreduceMotion, instead of fighting each widget's own built-in animation in place.LazyAllyColorBlindSwitch/LazyAllyReduceMotionSwitch(Switch) have no equivalent swap. Apps that need a fully motion-free control can build one directly withreduceMotionListenable/LazyAllyReducedMotionBuilder, the same tool these widgets themselves are built on.LazyAllyCustomSchemeSelector/LazyAllyCustomThemeSelectorare not part ofLazyAllyPanel. Copy is a product decision. Both are builders:itemBuilderhands you(context, name, selected, onSelect)per entry (plus a leadingnull"none selected" slot), with a plainChoiceChipfallback if omitted. They render nothing (plus a debug-only nudge) when their registry is empty. That is optional, not a misconfiguration.
lazyAllyOnColorFor(Color)is a small exported utility (legible black/white foreground for a given background) useful when building your owncolorBlindSchemeorcustomColorSchemesentries.