feat(ux): apply tokens to 5 deferred screens + CompletionBloom#17
Open
Outtsett wants to merge 8 commits intodesign/research-grounded-systemfrom
Open
feat(ux): apply tokens to 5 deferred screens + CompletionBloom#17Outtsett wants to merge 8 commits intodesign/research-grounded-systemfrom
Outtsett wants to merge 8 commits intodesign/research-grounded-systemfrom
Conversation
- Per-page accent: sky for welcome/notifications, forest for identity/health - Tinted halo (140dp circle) behind hero icon — visible Lichtenfeld 2012 brief-glimpse priming on the identity page - AnimatedPageDot: active dot stretches to 24dp pill (was 12dp circle) - All paddings tokenized via Spacing.s* - Page transition curve unified with Motion.bloomCurve Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Identity field wrapped in forest-tinted card with vote icon (Clear 2018 identity-based change + Lichtenfeld 2012 priming at decision point) - Swatches drawn from research-grounded design ramps (sky/forest/amber/ crimson/slate.700) plus 3 extended hues (violet/pink/teal.700) — all audited at 4.5:1+ legibility - Animated swatch selection (36->44dp on select with shadow bloom) - Animated icon tile selection (forest-tinted bg + primary border) - Mono digits on time-of-day display + skip-tolerance count + slider active color = success (green at the safety-margin decision) - 2-min version helper text quotes Atomic Habits explicitly - All paddings tokenized via Spacing.s* Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…et polish
- Three mono-digit stat tiles above heatmap: success-rate (forest),
attempts (sky primary), high-risk count (amber when >0)
- Heatmap wrapped in surfaceVariant card; cells bumped 14->16dp for
hero-element treatment
- IF-THEN bottom sheet:
* forest-tinted header card with psychology icon (Lichtenfeld
priming for the proposal-authoring task)
* mono digits on skip-risk % and attempts count
* 'Save plan' FilledButton.icon with checkmark
* top drag handle for sheet affordance
- High-risk windows card: amber-tinted bg + border, mono % digits,
amber dot per-row, chevron + InkWell tap target
- Upsell card: sky-tinted with bolt icon, quotes Gollwitzer d=0.65
- No-data state: insights icon halo + tokens.primary 0.5 alpha,
helper copy in muted color
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…enefits - Hero card: sky->forest gradient bg (Mehta-Zhu approach + Lichtenfeld completion ecosystem); '$6.99' rendered as Plus Jakarta + JetBrains Mono in 56pt mono with tabular figures; tagline + 'no subscription' iconified inline - Sticky bottom CTA section with surface border so the FilledButton is always reachable without scrolling (the conversion moment must not be hidden by a scroll) - 'Unlock for $6.99' button uses inline Text.rich so the price stays mono while the verb stays Plus Jakarta Sans - Per-benefit accent color routes by domain: primary (sky) for habits/ themes, warning (amber) for analytics, success (forest) for IF-THEN/ health, danger (crimson) for ads-block, muted slate for local-first - 40dp icon tile with 12% alpha accent bg replaces flat icon — gives the list visual rhythm and pairs with the streak-chip token semantics - 'Premium unlocked' banner forest-tinted with check icon when entitled Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… zone - Section headers: uppercase + letter-spacing 1.2 + lead icon (palette, bell, heart, brain, premium-badge, storage, info), token primary accent - Theme rows: 44x28dp two-color preview pill (surface + primary + secondary) using the actual ThemeData a user would get on commit; selected check icon (forest); locked rows show price in mono digits - IF-THEN rows: adherence % rendered mono with traffic-light color (success >= 70, warning >= 40, danger below) - Subscription status: chip with success bg when premium, surfaceVariant when free, lifetime price uses bolt icon prefix - Data section: danger zone wrapped in crimson-bordered box with subtle bg tint — visually separated from normal export action - About: version + source rendered in JetBrains Mono with tabular figures Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New widget lib/widgets/completion_bloom.dart: one-shot OverlayEntry with RadialGradient painted at the tapped card's global center. Animation: 400ms easeOutCubic, radius 12->332dp, alpha 0.30->0 (Lichtenfeld 2012 brief-glimpse priming via tokens.identityDot forest.500). Self-cleaning entry, IgnorePointer guards against swallowing rapid double-taps. - HomeScreen: wrap _HabitCard with Builder to scope a cardContext, capture the RenderBox center before the await on HabitProvider.toggleCompletion, then fire the bloom only when the toggle resolves nowCompleted=true (no bloom on un-toggle). - Bloom color = tokens.identityDot (forest.500 light / forest.400 dark) so it matches the identity-vote dot already shown on the voted line. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* context.tokens now returns SemanticColors.light when the theme extension isn't present (e.g. widget tests that pump a bare MaterialApp without our AppTheme.light()). Production code still reads the registered values; widget tests no longer have to wire the theme just to use a single token. * add_habit_screen_test: identity field's labelText was replaced with a hintText when the forest-tinted identity card landed; test now addresses the three TextFormFields by index (0=name, 1=identity, 2=two-minute) instead of label text. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Applies the research-grounded design token system (context.tokens, Spacing, AppTypography) across the previously deferred UI surfaces (Onboarding, AddHabit, Analytics, Premium, Settings) and introduces a completion “radial bloom” micro-interaction triggered from the Home habit card.
Changes:
- Updated multiple screens to use design tokens for spacing, color, and typography (including new section headers, stat tiles, paywall layout, and onboarding accents).
- Added
CompletionBloomoverlay and integrated it intoHomeScreenon habit completion. - Made
context.tokensmore resilient by adding a fallback when the theme extension is missing; adjusted widget test finders for AddHabit.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/screens/onboarding_screen.dart | Applies tokenized spacing/motion and per-page accent/glow styling for onboarding pages. |
| lib/screens/add_habit_screen.dart | Tokenizes spacing/colors/typography; introduces identity “card” and animated swatch/icon selection UI. |
| lib/screens/analytics_screen.dart | Adds tokenized summary stat tiles, rewraps heatmap, and upgrades IF-THEN bottom sheet styling/actions. |
| lib/screens/premium_screen.dart | Reworks paywall layout with sticky CTA and tokenized benefit tiles/hero pricing typography. |
| lib/screens/settings_screen.dart | Tokenizes section chrome and adds theme preview swatches + richer subscription/data/about presentation. |
| lib/screens/home_screen.dart | Hooks completion bloom into habit completion interaction (captures tap center + shows overlay). |
| lib/widgets/completion_bloom.dart | New overlay-driven radial bloom animation painter for completion micro-interaction. |
| lib/design/tokens.dart | Adds context.tokens fallback behavior when SemanticColorsExt is not registered. |
| test/add_habit_screen_test.dart | Updates form field targeting approach to accommodate identity field UI changes. |
Comment on lines
303
to
+305
| SemanticColors get tokens => | ||
| Theme.of(this).extension<SemanticColorsExt>()!.colors; | ||
| Theme.of(this).extension<SemanticColorsExt>()?.colors ?? | ||
| SemanticColors.light; |
Comment on lines
+192
to
+196
| final renderBox = | ||
| cardContext.findRenderObject() as RenderBox?; | ||
| final globalCenter = renderBox?.localToGlobal( | ||
| renderBox.size.center(Offset.zero), | ||
| ); |
Comment on lines
+382
to
+388
| decoration: const InputDecoration( | ||
| hintText: 'a runner, a writer, a meditator…', | ||
| helperText: | ||
| 'each completion casts a vote for this identity', | ||
| border: OutlineInputBorder(), | ||
| filled: true, | ||
| fillColor: Colors.white, |
Comment on lines
+232
to
+286
| return InkWell( | ||
| onTap: onTap, | ||
| child: Padding( | ||
| padding: const EdgeInsets.symmetric( | ||
| horizontal: Spacing.s4, | ||
| vertical: Spacing.s3, | ||
| ), | ||
| child: Row( | ||
| children: [ | ||
| _ThemeSwatch(preview: preview, isLocked: isLocked), | ||
| const SizedBox(width: Spacing.s4), | ||
| Expanded( | ||
| child: Column( | ||
| crossAxisAlignment: CrossAxisAlignment.start, | ||
| children: [ | ||
| Text( | ||
| label, | ||
| style: Theme.of(context).textTheme.bodyLarge?.copyWith( | ||
| color: isLocked ? lockedColor : tokens.onSurface, | ||
| fontWeight: isSelected | ||
| ? FontWeight.w600 | ||
| : FontWeight.w400, | ||
| ), | ||
| ), | ||
| if (isLocked) | ||
| Text.rich( | ||
| TextSpan( | ||
| style: Theme.of( | ||
| context, | ||
| ).textTheme.bodySmall?.copyWith(color: lockedColor), | ||
| children: [ | ||
| const TextSpan(text: 'Premium · '), | ||
| TextSpan( | ||
| text: '\$6.99', | ||
| style: AppTypography.mono( | ||
| color: lockedColor, | ||
| fontSize: 12, | ||
| fontWeight: FontWeight.w700, | ||
| ), | ||
| ), | ||
| const TextSpan(text: ' lifetime'), | ||
| ], | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| if (isLocked) | ||
| Icon(Icons.lock_outline, size: 18, color: lockedColor) | ||
| else if (isSelected) | ||
| Icon(Icons.check_circle, size: 22, color: tokens.success), | ||
| ], | ||
| ), | ||
| ), | ||
| ); |
Comment on lines
+143
to
+150
| // The first TextFormField is "Habit name", second is the identity | ||
| // field (now wrapped in a forest-tinted card with a heading row, so | ||
| // its labelText was replaced with a hintText — the test addresses | ||
| // the field by index instead of label), third is "2-minute version". | ||
| final fields = find.byType(TextFormField); | ||
| expect(fields, findsAtLeastNWidgets(3)); | ||
| await tester.enterText(fields.at(0), 'Read'); | ||
| await tester.enterText(fields.at(1), ' reader '); |
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Continues the design system from PR #16 by applying
context.tokensto the 5 deferred screens (Onboarding, AddHabit, Analytics, Premium, Settings), plus a new radial-bloom micro-interaction on habit complete.Stacked on PR #16 (
design/research-grounded-system). Merge #16 first; this rebases cleanly ontomainafter.Per-screen highlights
New: CompletionBloomOverlay
lib/widgets/completion_bloom.dart— one-shot OverlayEntry that paints a radial gradient at the tapped card's global center on habit complete. 400ms easeOutCubic, radius 12->332dp, alpha 0.30->0. Color =tokens.identityDot(forest.500 light / forest.400 dark) so it matches the identity-vote dot already on the voted line. Self-cleaning entry, IgnorePointer guards rapid double-taps.Token system polish
context.tokensnow falls back toSemanticColors.lightwhen theSemanticColorsExttheme extension isn't registered — keeps widget tests that pump a bareMaterialApp(without ourAppTheme.light()) from blowing up.Test plan
flutter analyze --fatal-infos— cleanflutter test— 62 pass / 2 skip (was 62/2 on PR feat(design): research-grounded token system + flagship HomeScreen #16; one existing test updated to address fields by index since the AddHabit identity field's labelText moved to a heading row)flutter build apk --debug— Builtapp-debug.apkFiles
lib/screens/onboarding_screen.dartlib/screens/add_habit_screen.dartlib/screens/analytics_screen.dartlib/screens/premium_screen.dartlib/screens/settings_screen.dartlib/screens/home_screen.dart(CompletionBloom integration)lib/widgets/completion_bloom.dartlib/design/tokens.dart(graceful fallback)test/add_habit_screen_test.dart(resilient field finder)🤖 Generated with Claude Code