hintful 0.7.0
hintful: ^0.7.0 copied to clipboard
Onboarding tours and tooltips for Flutter. Registry-based targets, theme-aware coach marks, smart positioning, accessible by default — zero cost when idle.
Changelog #
0.7.0 — honest presets, tolerant JSON, tighter surface #
Breaking: HintSkipReason.targetUnmountedDuringStep is gone — drop the case
from an exhaustive switch — and four engine symbols leave the public barrel.
HintCurve.easeOutis a real preset: fade + scale 0.96 → 1 onCurves.easeOut, 200 ms. It used to behave like "no animation".fromJsontolerates an unknown enum name: it falls back to the field default and is reported through the newonWarningcallback (also onFetcherHintTourFactory) instead of throwing.- Exported
hintTransitionDuration— the reduce-motion helper the presets use, so a customtooltipBuildershares the same contract. - Duplicate
targetIdwarnings now actually print in debug builds;onWarningstill fires. autoScrollno longer flashes the tooltip in a screen corner: while the target is still off screen the tooltip waits at the edge it is coming from and rides the scroll onto it. The position watch dropped the tooltip for the whole animation — an unpainted target has no compositor transform — and the placement parked it in the top-left corner.- Removed
findClosestTargetId;formatHintSkipped,editDistance,CompositorHintResolverandUnpositionedHintResolverare no longer exported. They stay public insidelib/engine/for the package's own tests, and custom hosts keepHintPosition,PositionedHint,UnpositionedHintandHintPositionResolver. - Dropped the stale
hintful_bloc/references from.pubignoreandanalysis_options.yaml.
Docs: README rebuilt around badges, a demo section and the withHint/l10n paths;
doc/best_practices.md now covers 0–21 with an index; the FAQ grew to ten answers.
0.6.2 — autoScroll, withHint, target focus and l10n #
autoScrollonHintTour/HintStep(opt-in,falseby default) — brings offscreen targets into view.HintTarget(focusShape/padding)as target default —HintStepoverrides; no per-step duplication for round icons.withHintextension:child.withHint('id')sugar overHintTarget.titleBuilder/descriptionBuilderonHintStep/HintTooltip— l10n viaBuildContextwithout threading it throughAppTours.- Example:
Step scroll(per-stepautoScroll),L10n(builder),withHintonfilter-all,entry-5circle, introautoScroll: true, full customTweenAnimationBuilder+ float.
0.6.1 — flicker-free spotlight #
- First-frame seed from RenderBox — no white flash on start/step change.
- Global scrim — no bottom gap on scroll.
- Scroll-synced holes — scrim + tooltip move with content in same frame.
0.6.0 — spotlight correctness (saveLayer scrim, rect targets, honest states) #
- First-frame content: positions seed synchronously from the targets'
own render objects (
initState+ step changes), so dim + hole + tooltip render on the very first frame — no normal-UI flash on start, no stale step on transitions. Scroll-driven motion follows the compositor synchronously (ancestor Scrollables observed) — no one-frame lag while the screen scrolls. Live compositor resolvers upgrade in behind (same values ± subpixel). - One scrim mechanism for every shape: fullscreen dim +
BlendMode.clearholes in an isolated layer (no boolean geometry, overlap-correct); blur clips to one even-odd path. Removed the strips path (scrimStrips, pre-1.0 breaking note). - First shown step renders dim + hole together with the tooltip (was: tooltip without dim until a step change).
targetRectsteps: immediate Active, static spotlight with tooltip; explicitoverlayprovider for zero-target tours.- Offscreen/culled targets: no frozen spotlight — retracted while unpainted, remounted on return; bringing targets into view stays the app's job. No stale tooltip on step change (transition frame unmounts).
- Single-step hints keep no action row (no meaningless Done).
- Hardened holes: over-shrunk padding degrades to full dim, corner radius clamped, never throws.
- DRY: one hole geometry, one tooltip content/placement/entry source,
FocusShapeend to end, one machine step-entry path. - Accessibility: sprung honors reduce-motion; offer dialog awaits start.
0.5.0 — production hardening (l10n, skip-missing, safe start, scopes) #
Four battle-feedback fixes, all backward-compatible:
- Localization hook:
HintTooltipLabels(engine/labels.dart) —skip/ back/next/done, waiting placeholderpreparing, screen-readerannounceStep. Wired asHintTheme.tooltipLabels(English default) + a per-tooltipDefaultTooltip(labels:)override — localize once in the theme instead of duplicating the tooltip layout per language. The waiting-phase "Preparing…" comes from the labels too. - Missing-target policy:
HintMissingTargetPolicy.skipStepvsabortTour(default) — onHintTour(incl.fromEnum, JSON) with a per-step override. A timed-out step is diagnosed (timeout) and the tour continues; skipping the last step finishes normally. Pair with a shortwaitTimeout(Duration.zeroskips instantly, no waiting flash). New machine effectStepSkippedEffect. - Safe start:
isIdle,tryStart(false when busy — no assert, no state change),restart(silently replaces the running tour),tryShowHint.startkeeps its debug contract. - Controller scopes:
scopePrefixisolates tabs/split-view sharing one registry — foreign ids neither activate steps nor count as typo candidates.
0.4.0 — server-driven tours + adapters #
HintTour/HintStep/HintTooltipnowfromJson/toJson(specs.dart:194) —stepsaretitle/description+position/moreTargets/moreTooltips,tooltipBuilderstays code-side.FetcherHintTourFactory(engine/tour_factory.dart) takes your fetcher(Uri)=>Future<String>— nohttpdependency inhintful.- Adapters
lib/src/adapters/{bloc,riverpod,provider,getx}.dart— thinValueListenable→Cubit/Providerstubs (~15 lines) overHintController.state, core staysdart:ui+widgets.
0.3.0 — control, accessibility, versions, visual depth #
New capabilities
- Programmatic control:
previous()/goTo(),HintTour.disableBackButton(Android back/route pop), Shift+Tab backwards navigation. - Smart positioning (full): auto-flip re-picks the side on scroll, keep-in-safe-area (notch/home indicator), the tooltip never covers the spotlighted targets, tail (arrow) ties the tooltip to its target.
- Accessibility on by default: screen-reader step announcements, keyboard
navigation (Tab/Shift+Tab/Enter, Esc = skip), reduce-motion
(
hintTransitionDuration), fits at 2× text scale, WCAG AA contrast in the default themes, focus restored after the tour. - Versioned hints:
HintStore(shouldShow(key, minVersion:)/markShown), the "show again" semantic is a version bump, not flag wiping;InMemoryHintStoreships in the core, persistent implementations live app-side. - Multi-target steps: several elements spotlighted at once, each with its own scrim hole. Multi-content: several tooltips around one target that never overlap each other or the targets.
- Tap regions: tap-on-target vs tap-on-overlay with per-step callbacks and tap position; scroll-through — the page scrolls under an active tour.
- Blur scrim and a pulsing ring as theme options (
HintTheme.imageFilter/showPulse); the default stays the cheap plain dim. The pulse renders above the blur (its own global layer). - Enum-typed tours:
HintTour.fromEnum— the exhaustivestepForswitch makes adding/removing a step a compile error. - "Want a tour?" pre-dialog:
showHintTourOfferwith an "Apply to all pages" checkbox; declines persist per page or globally in the store. - UX polish: Skip is hidden on the last step of a tour (a lone Skip was already meaningless in 0.2.0); the scrim no longer flashes a wrong full-screen dim on the first frame of a step.
- Example app reworked into a demo playground (
main/home_screen/demo_tours): visual demo card with blur/pulse style switcher and one button per feature, 10 smoke tests.
Adapters
hintful_bloc0.1.1:HintCubit— a thin Cubit overHintController(no logic of its own; apps wanting an event layer write their ownBloc<AppEvent, HintState>on top).
0.2.0 — unified "hint" naming #
Breaking: the public API is renamed to a single Hint family —
ShowcaseController → HintController, ShowcaseTarget → HintTarget,
ShowcaseTheme → HintTheme, TourState → HintState (and Idle/
Waiting/Active), TourSpec/StepSpec → HintTour/HintStep,
TargetRegistry → HintTargetRegistry, TourOverlayHost → HintOverlayHost.
Files showcase_target.dart/showcase_theme.dart → hint_target.dart/
hint_theme.dart. TooltipPosition, DefaultTooltip and diagnostics
(HintSkipReason, HintDiagnosticsHandler) keep their names — tooltip is
the accepted term for the visual element, diagnostics were already Hint.
No behavior changes.
0.1.0 — stage 0 (early engine) #
- Registry-based targets (
ShowcaseTarget(id:)) — noGlobalKey; duplicate-id policy "last wins", self-cancellation by identity indispose. - Clean state machine (
TourState), data-driven tests (table + fuzz). ShowcaseController:start/next/skip/finish,showHintfor single tips, DX3 validation with closest-id candidates for typos; headless-capable (no overlay host required).- Overlay engine on
CompositedTransform: scrim hole follows the target via the compositor (zero scroll math), tooltip in a global layer with auto-flip placement (stage 0: sides + keep-in-screen). - Wait-for-target with timeout, deferred/lazy targets.
ShowcaseThemeThemeExtension, light/dark fromColorScheme.- DX1 diagnostics: every failed show reports a reason (
timeout,userSkipped,unknownTarget, ...) to aHintDiagnosticsHandler. TourOverlayEngine(hidden mechanics) +defaultOverlayHost()factory; keyboard: Tab/Enter = next, Esc = skip.- Example app (
example/): 4-step tour with deferred target, light/dark,showHint; smoke tests, tour flow tests, integration benchmark skeleton.