feat: add dynamic color theming from wallpaper - #18
Conversation
On Android 12+ (and Samsung devices with wallpaper-based theming), the widget now adapts its colors to match the system's dynamic color scheme derived from the wallpaper. The contribution grid uses shades of the theme's primary color instead of always being green, and the card background, text, and accent colors follow the system theme. Falls back to the original dark/light green palettes on devices without dynamic color support. Changes: - Add dynamic_color dependency - WidgetPalette.fromColorScheme() derives palette from ColorScheme - ThemeProvider propagates dynamic colors through the widget tree - WidgetCard resolves palette from ThemeProvider when available - WidgetUpdater passes dynamic scheme when rendering the widget image - MainActivity updated for dynamic color support
|
Warning Review limit reached
Next review available in: 12 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThe app now derives dynamic color schemes, exposes them through ChangesDynamic color theming
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/core/theme.dart`:
- Around line 46-69: Update lib/core/theme.dart lines 46-69 by making
ThemeProvider.scheme nullable while preserving maybeOf(context)'s null result
and allowing of(context) to retain its existing fallback. Update lib/main.dart
lines 21-31 to select lightDynamic or darkDynamic based on the active
brightness/theme mode and pass null when neither dynamic scheme exists, rather
than synthesizing a seed scheme.
In `@lib/features/preview/preview_screen.dart`:
- Around line 39-45: Keep the selected brightness and dynamic scheme aligned: in
lib/features/preview/preview_screen.dart lines 39-45, resolve and pass the
scheme matching _effectiveMode, preferably retaining both light and dark
schemes; in lib/features/preview/widget_card.dart lines 91-94, reject or safely
fall back when dynamic.brightness differs from brightness.
In `@lib/features/preview/widget_card.dart`:
- Around line 91-94: Update _resolvePalette and the surrounding theme-resolution
flow to distinguish ColorSchemes sourced from DynamicColorBuilder from
ThemeProvider’s static fallback seed. Only use WidgetPalette.fromColorScheme for
an actual dynamic-color scheme; otherwise preserve the existing
WidgetPalette.of(brightness) static palette behavior, including when
ThemeProvider is present.
In `@lib/main.dart`:
- Around line 157-164: Update the deferred callback in the _pushedToWidget flow
to await WidgetUpdater.update and catch its Future failures, recording the error
and resetting or retrying the push with a bounded retry policy. Ensure failures
do not remain unhandled and successful updates retain the existing one-time
behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e392814a-6c2c-49e6-b753-f176ad4e4f0c
⛔ Files ignored due to path filters (1)
pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (8)
android/app/src/main/kotlin/com/rakshityadav/github_widget/MainActivity.ktlib/core/colors.dartlib/core/theme.dartlib/data/services/widget_updater.dartlib/features/preview/preview_screen.dartlib/features/preview/widget_card.dartlib/main.dartpubspec.yaml
| const ThemeProvider({ | ||
| super.key, | ||
| required this.scheme, | ||
| required super.child, | ||
| }); | ||
|
|
||
| final ColorScheme scheme; | ||
|
|
||
| static ColorScheme? maybeOf(BuildContext context) { | ||
| final result = | ||
| context.dependOnInheritedWidgetOfExactType<ThemeProvider>(); | ||
| return result?.scheme; | ||
| } | ||
|
|
||
| static ColorScheme of(BuildContext context) { | ||
| final result = | ||
| context.dependOnInheritedWidgetOfExactType<ThemeProvider>(); | ||
| return result?.scheme ?? | ||
| ColorScheme.fromSeed(seedColor: WidgetPalette.dark.accentGreen); | ||
| } | ||
|
|
||
| @override | ||
| bool updateShouldNotify(ThemeProvider old) => | ||
| old.scheme != scheme; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)theme\.dart$|(^|/)main\.dart$|pubspec\.yaml$' || true
echo "== theme outlines =="
for f in $(git ls-files | rg '(^|/)theme\.dart$'); do
echo "-- $f --"
wc -l "$f"
ast-grep outline "$f" || true
sed -n '1,140p' "$f"
done
echo "== main snippets =="
for f in $(git ls-files | rg '(^|/)main\.dart$'); do
echo "-- $f --"
wc -l "$f"
sed -n '1,90p' "$f"
done
echo "== dynamic color usages =="
rg -n "DynamicColorBuilder|maybeOf|WidgetPalette\.dark|WidgetPalette\.light|ColorScheme\.fromSeed|ThemeMode" .
echo "== pubspec dynamic/deps =="
if [ -f pubspec.yaml ]; then
sed -n '1,160p' pubspec.yaml
fiRepository: rakshityadav1868/github-widget
Length of output: 10225
🌐 Web query:
pub.dev packages dynamic_color 1.7.0 builder signature returns lightDynamic darkDynamic
💡 Result:
In version 1.7.0 of the dynamic_color package, the builder signature for the DynamicColorBuilder widget was updated to provide ColorScheme objects directly [1]. The signature for the builder is [2][3]: builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) { return...; } Starting with version 1.7.0, the builder provides lightDynamic and darkDynamic as ColorScheme? types [1]. This was a change from previous versions, which provided CorePalette objects [1]. Users requiring the underlying CorePalette can still access it using the DynamicColorPlugin.getCorePalette method [1].
Citations:
- 1: https://pub.dev/packages/dynamic_color/versions/1.7.0/changelog
- 2: https://pub.dev/packages/dynamic_color/versions/1.7.0
- 3: https://pub.dev/documentation/dynamic_color/latest/dynamic_color/DynamicColorBuilder-class.html
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== widget card relevant section =="
sed -n '70,115p' lib/features/preview/widget_card.dart
echo "== preview screen relevant section =="
sed -n '25,50p' lib/features/preview/preview_screen.dart
echo "== colors outline =="
ast-grep outline lib/core/colors.dart || true
sed -n '1,220p' lib/core/colors.dartRepository: rakshityadav1868/github-widget
Length of output: 6139
Preserve the missing dynamic-color signal and use the active theme.
DynamicColorBuilder passes light/dark dynamic schemes directly, but this app collapses unsupported dynamic colors into a synthesized seed scheme and discards the second scheme, so maybeOf(context) treats unsupported devices as if dynamic colors are available.
lib/main.dart#L21-31: uselightDynamic/darkDynamic, choose the scheme forbrightness: MediaQuery.platformBrightnessOf(context)orthemeMode, and passnullwhen neither dynamic scheme is available instead of creatingColorScheme.fromSeed(...).lib/core/theme.dart#L46-56: makeThemeProvider.schemenullable somaybeOf(context)can represent unavailable dynamic colors and existing static palette fallbacks are not bypassed.
📍 Affects 2 files
lib/core/theme.dart#L46-L69(this comment)lib/main.dart#L21-L31
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/core/theme.dart` around lines 46 - 69, Update lib/core/theme.dart lines
46-69 by making ThemeProvider.scheme nullable while preserving
maybeOf(context)'s null result and allowing of(context) to retain its existing
fallback. Update lib/main.dart lines 21-31 to select lightDynamic or darkDynamic
based on the active brightness/theme mode and pass null when neither dynamic
scheme exists, rather than synthesizing a seed scheme.
| if (!_pushedToWidget) { | ||
| _pushedToWidget = true; | ||
| WidgetsBinding.instance.addPostFrameCallback((_) { | ||
| WidgetUpdater.update( | ||
| snapshot.data!, | ||
| dynamicScheme: dynamicScheme, | ||
| ); | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Handle failures from the deferred widget render.
WidgetUpdater.update is fire-and-forget here. A platform/render failure becomes unhandled, while _pushedToWidget remains true and prevents any later retry. Catch and record the Future failure, then apply a bounded retry/reset policy.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/main.dart` around lines 157 - 164, Update the deferred callback in the
_pushedToWidget flow to await WidgetUpdater.update and catch its Future
failures, recording the error and resetting or retrying the push with a bounded
retry policy. Ensure failures do not remain unhandled and successful updates
retain the existing one-time behavior.
WidgetCard and WidgetUpdater's renderer were pulling the dynamic ColorScheme from ambient context (ThemeProvider.maybeOf) unconditionally whenever it was available, ignoring the brightness the caller explicitly asked for. On any Android 12+ or Samsung device, this meant the preview screen's Light/Dark toggle - and "Add light/dark widget to home" - had no effect: the widget always rendered with the dynamic wallpaper scheme regardless of what was selected. WidgetCard now takes dynamicScheme as an explicit constructor parameter instead of reading it from context itself, so the caller controls when wallpaper matching applies. PreviewScreen gets a third explicit choice, "Theme match", alongside Light/Dark - selecting Light or Dark now always forces that static palette, matching the toggle's existing (pre-PR) contract. Also: - Light-mode card border now derives from the dynamic scheme's outlineVariant when theme-matching is active, instead of a fixed gray that could clash with the wallpaper palette. - Removed a docstring claim in _deriveGridLevels about biasing hue toward green that wasn't actually implemented. - Removed MainActivity's onCreate override, which only called super and wasn't needed for dynamic_color (a pure Dart/platform-channel plugin). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts: # lib/main.dart # pubspec.yaml
|
@rakshityadav1868 is attempting to deploy a commit to the yadavrakshit60-2354's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
Reviewed and tested this - great feature idea. Found one functional bug during testing and fixed it directly on this branch (maintainer-edit was enabled, thanks for that): Bug: Fix: Also cleaned up while in there:
Verified: |
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
On Android 12+ (and Samsung devices with wallpaper-based theming), the widget now adapts its colors to match the system's dynamic color scheme derived from the wallpaper. The contribution grid uses shades of the theme's primary color instead of always being green, and the card background, text, and accent colors follow the system theme.
Falls back to the original dark/light green palettes on devices without dynamic color support.
Approach:
dynamic_colorpackage to detect wallpaper-derived colors viaDynamicColorBuilderWidgetPalette.fromColorScheme()derives the full widget palette (card bg, grid levels, text, accent) from the system'sColorSchemeThemeProvider(InheritedWidget) propagates the resolved color scheme through the widget treeWidgetCardresolves its palette fromThemeProviderwhen available, falling back to static dark/lightWidgetUpdaterpasses the dynamic scheme when rendering the widget image so the home-screen widget matchesMainActivityupdated withonCreatefor dynamic color supportTesting:
flutter test)flutter analyzereports no issuesSummary by CodeRabbit