Skip to content

feat: add dynamic color theming from wallpaper - #18

Merged
rakshityadav1868 merged 3 commits into
rakshityadav1868:mainfrom
Dreamstick9:dreamstick9-dynamic-color-theming
Jul 24, 2026
Merged

feat: add dynamic color theming from wallpaper#18
rakshityadav1868 merged 3 commits into
rakshityadav1868:mainfrom
Dreamstick9:dreamstick9-dynamic-color-theming

Conversation

@Dreamstick9

@Dreamstick9 Dreamstick9 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

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:

  • Added dynamic_color package to detect wallpaper-derived colors via DynamicColorBuilder
  • WidgetPalette.fromColorScheme() derives the full widget palette (card bg, grid levels, text, accent) from the system's ColorScheme
  • ThemeProvider (InheritedWidget) propagates the resolved color scheme through the widget tree
  • WidgetCard resolves its palette from ThemeProvider when available, falling back to static dark/light
  • WidgetUpdater passes the dynamic scheme when rendering the widget image so the home-screen widget matches
  • MainActivity updated with onCreate for dynamic color support

Testing:

  • All 9 existing tests pass (flutter test)
  • flutter analyze reports no issues
  • Manual testing requires a real Android 12+ device with a colorful wallpaper

Summary by CodeRabbit

  • New Features
    • Added dynamic color support based on system or wallpaper themes on supported Android devices.
    • Updated the app and home-screen widget to use matching dynamic color palettes.
    • Added light and dark theme color generation with graceful fallback when dynamic colors are unavailable.
    • Ensured widget updates apply the selected theme consistently.

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
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rakshityadav1868, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bce43015-d9a9-44a1-b60d-78e77c222bbe

📥 Commits

Reviewing files that changed from the base of the PR and between 3fbe78b and 975b6b9.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • lib/core/colors.dart
  • lib/data/services/widget_updater.dart
  • lib/features/preview/preview_screen.dart
  • lib/features/preview/widget_card.dart
  • lib/main.dart
  • pubspec.yaml
📝 Walkthrough

Walkthrough

The app now derives dynamic color schemes, exposes them through ThemeProvider, resolves widget palettes from ColorScheme, and passes schemes into home-screen and preview widget rendering with brightness-based fallbacks.

Changes

Dynamic color theming

Layer / File(s) Summary
Dynamic palette and theme provider
lib/core/colors.dart, lib/core/theme.dart
WidgetPalette derives widget colors and grid levels from a ColorScheme, while ThemeProvider exposes the scheme to descendants.
App and platform scheme integration
pubspec.yaml, android/app/src/main/kotlin/.../MainActivity.kt, lib/main.dart
The dynamic-color dependency and Android lifecycle setup are added; DynamicColorBuilder supplies the resolved scheme through ThemeProvider, and loaded stats are pushed to the widget once.
Dynamic widget rendering
lib/data/services/widget_updater.dart, lib/features/preview/preview_screen.dart, lib/features/preview/widget_card.dart
Widget updates carry an optional scheme, and WidgetCard resolves dynamic palettes with brightness fallback for home-screen and preview rendering.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: rakshityadav1868

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding wallpaper-derived dynamic color theming.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2ffcbd1 and 3fbe78b.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • android/app/src/main/kotlin/com/rakshityadav/github_widget/MainActivity.kt
  • lib/core/colors.dart
  • lib/core/theme.dart
  • lib/data/services/widget_updater.dart
  • lib/features/preview/preview_screen.dart
  • lib/features/preview/widget_card.dart
  • lib/main.dart
  • pubspec.yaml

Comment thread lib/core/theme.dart
Comment on lines +46 to +69
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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
fi

Repository: 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:


🏁 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.dart

Repository: 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: use lightDynamic / darkDynamic, choose the scheme for brightness: MediaQuery.platformBrightnessOf(context) or themeMode, and pass null when neither dynamic scheme is available instead of creating ColorScheme.fromSeed(...).
  • lib/core/theme.dart#L46-56: make ThemeProvider.scheme nullable so maybeOf(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.

Comment thread lib/features/preview/preview_screen.dart Outdated
Comment thread lib/features/preview/widget_card.dart Outdated
Comment thread lib/main.dart
Comment on lines +157 to +164
if (!_pushedToWidget) {
_pushedToWidget = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
WidgetUpdater.update(
snapshot.data!,
dynamicScheme: dynamicScheme,
);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

rakshityadav1868 and others added 2 commits July 24, 2026 22:08
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
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

@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.

@rakshityadav1868

Copy link
Copy Markdown
Owner

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: 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 existing 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. Manually forcing light or dark, a feature that worked before this PR, silently broke on the majority of modern Android devices.

Fix: 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 now has a third explicit choice, "Theme match", alongside Light/Dark (only shown when dynamic color is actually available on the device) - selecting Light or Dark always forces that static palette now, matching the toggle's original contract.

Also cleaned up while in there:

  • 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).
  • Merged main in to resolve conflicts from background-refresh/signing work that landed after this branch was created.

Verified: flutter analyze clean, all tests passing, and a full signed flutter build apk --release succeeds. No Android 12+/Samsung device was available to eyeball actual wallpaper colors, but the logic was traced through by hand and the fix directly targets the bug found.

@rakshityadav1868
rakshityadav1868 merged commit 201e4b3 into rakshityadav1868:main Jul 24, 2026
1 of 2 checks passed
rakshityadav1868 added a commit that referenced this pull request Jul 24, 2026
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants