Skip to content

feat(ui): Add Miuix blur and custom card backdrop blur support - #219

Closed
fanziyun wants to merge 21 commits into
devfrom
codex/miuix-blur-port
Closed

feat(ui): Add Miuix blur and custom card backdrop blur support#219
fanziyun wants to merge 21 commits into
devfrom
codex/miuix-blur-port

Conversation

@fanziyun

@fanziyun fanziyun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Overview

This PR ports and integrates the miuix-blur-android library to add real-time frosted-glass blur effects to top bars and bottom navigation bars, along with software-based StackBlur support for custom background images on cards and tiles.

Key Changes

1. Miuix Blur Architecture (app/src/main/java/com/abk/kernel/ui/blur/)

  • BlurScreenScaffold & BlurHost: Redesigned page scaffold layouts to place body content inside the capture backdrop while keeping top bars outside the capture region, preventing render thread crashes (SIGSEGV) caused by self-referential blur sampling.
  • blurSource & blurEffect: Provided AGSL runtime shader blur (API 33+) for top bars and bottom navigation bars, automatically falling back to surface colors on unsupported devices.

2. Custom Background Card Blur (BlurredCardBackdrop.kt)

  • rememberBlurredCardBackground: Downsamples custom background images and pre-blurs them on Dispatchers.Default using StackBlur when "Render custom background into blur" is enabled.
  • blurredCardBackground: Allows cards and list items to sample their corresponding viewport rectangle for consistent background translucency.

3. Preferences & UI Performance (SettingsScreen.kt & MainViewModel.kt)

  • uiSurfaceAlphaPreview: Keeps drag state local during surface alpha slider drag, updating memory state per frame and deferring DataStore disk writes until the drag finishes (onValueChangeFinished).
  • Settings Controls: Added main "Blur" switch with expandable "Render custom background into blur" child toggle.

Verification & Testing

  • Unit Tests: Added BlurConfigTest to verify blur flag behavior.
  • Compatibility: Verified AGSL shader rendering on API 33+ and fallback logic on older versions.
  • Performance: Confirmed smooth slider dragging without I/O stuttering during surface opacity adjustments.

@fanziyun fanziyun changed the title feat(ui): Add Miuix blur and card backdrop support feat(ui): 引入 Miuix 毛玻璃模糊与卡片自定义背景模糊支持 Aug 11, 2026
@fanziyun fanziyun changed the title feat(ui): 引入 Miuix 毛玻璃模糊与卡片自定义背景模糊支持 feat(ui): Add Miuix blur and custom card backdrop blur support Aug 11, 2026

@xingguangcuicanbot xingguangcuicanbot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated PR Review

Summary

Found two actionable issues: reduced surface alpha persists after disabling the custom background, and optional card blur decodes unbounded source images with OOM risk.

Decision

request_changes

Notes

Codex recommendation: request_changes
Posted 2 inline review comment(s).

Comment thread app/src/main/java/com/abk/kernel/ui/components/AppBackgroundHost.kt
Comment thread app/src/main/java/com/abk/kernel/ui/blur/BlurredCardBackdrop.kt Outdated
Co-authored-by: xingguangcuican_bot <xingguangcuican821@gmail.com>

@xingguangcuicanbot xingguangcuicanbot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated PR Review

Summary

Two regressions: opaque-surface fallback is broken when no background image is enabled, and the software card blur fallback is incorrectly disabled on non-shader devices.

Decision

request_changes

Notes

Codex recommendation: request_changes
Posted 2 inline review comment(s).

Comment thread app/src/main/java/com/abk/kernel/ui/components/AppBackgroundHost.kt
Comment thread app/src/main/java/com/abk/kernel/ui/blur/BlurredCardBackdrop.kt Outdated
fanziyun and others added 3 commits August 11, 2026 21:47
…st.kt

Co-authored-by: xingguangcuican_bot <xingguangcuican821@gmail.com>
Co-authored-by: xingguangcuican_bot <xingguangcuican821@gmail.com>

@xingguangcuicanbot xingguangcuicanbot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated PR Review

Summary

The PR introduces a risky minSdk override for the new blur dependency, makes the default blur path effectively opaque unless UI opacity is lowered, and unintentionally disables the new software card-blur feature on pre-runtime-shader devices.

Decision

request_changes

Notes

Codex recommendation: request_changes
Posted 3 inline review comment(s).

Comment thread app/src/main/AndroidManifest.xml Outdated
Comment thread app/src/main/java/com/abk/kernel/ui/blur/BlurBackdrop.kt
Comment thread app/src/main/java/com/abk/kernel/ui/screens/SettingsScreen.kt
Make frosted glass visible and the software card-blur path usable on all APIs:

- Cap the bar blur tint alpha (AbkBlurTintAlpha) and keep card surfaces
  translucent when a pre-blurred custom background is present, instead of
  blending an opaque surfaceContainer over the backdrop at the default surface
  opacity, so the Blur switch has a visible effect by default.
- Decouple BlurConfig.wantsBackgroundPainter from the AGSL master switch and
  stop hiding the "render custom background into blur" toggle behind
  isBlurCapableDevice(), so API 26-32 devices can enable the StackBlur path.
- Turn off the bottom-bar backdrop and its blur pipeline while the bar is
  off-screen (child page or OOBE) to drop the invisible full-screen
  recordLayer + AGSL blur pass every frame.
- Replace per-card localToScreen/screenToLocal round-trips with
  LayoutCoordinates.localPositionOf; align blurSource/blurEffect guards to the
  actual API 33 runtime-shader capability instead of API 31.
- Debounce the card-background decode + StackBlur reload across viewport
  changes so rotation/split-screen do not re-run it per intermediate size.
- Share one background painter from AppBackgroundHost across all blur hosts
  instead of one Coil request per screen scaffold.
- Fix the surface-alpha preview state: an interrupted slider drag no longer
  sticks the in-memory preview for the session, and a late DataStore emission
  no longer yanks the slider thumb mid-drag.
- Drop dead LocalBlurredSurfaceDepth plumbing; route the extension manager
  through BlurConfig instead of duplicating its condition.

@xingguangcuicanbot xingguangcuicanbot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated PR Review

Summary

The blur integration is close, but the shared background rendering path in AppBackgroundHost introduces two regressions that affect wallpaper rendering and device stability.

Decision

request_changes

Notes

Codex recommendation: request_changes
Posted 2 inline review comment(s).

Comment thread app/src/main/java/com/abk/kernel/ui/components/AppBackgroundHost.kt
Comment thread app/src/main/java/com/abk/kernel/ui/components/AppBackgroundHost.kt
The painter-sharing change dropped the contentScale from the background
Image, so wallpapers with a mismatched aspect ratio were letterboxed with
the default Fit and the blur backdrops / card sampling could read the wrong
region of the wallpaper. Render with ContentScale.Crop to match the previous
AsyncImage behavior and the blur pipeline's crop math.

Coil's AsyncImagePainter sizes its request from the draw viewport
(size { drawSize...first() }), so the shared painter stays bounded to screen
resolution; a large user wallpaper is not decoded at its original dimensions.
Follow-up recheck of the blur subsystem (independent multi-agent audit +
manual verification):

- Fix the bottom-bar blur gate direction: navProgress is 1f when the bar is
  shown and animates to 0f when a child page hides it, so the previous
  `navProgress < 1f` gate disabled blur in the normal tab state and kept it
  running while the bar was fully off-screen. Gate on `navProgress > 0.02f`.
- Restore the master Blur switch as the overall on/off for every blur surface:
  show it on all API levels and re-tie BlurConfig.wantsBackgroundPainter to
  blurEnabled, so turning the master off disables the card blur too (it was
  decoupled in a prior commit, leaving frosted cards with opaque bars).
- Stop swallowing CancellationException and OutOfMemoryError in the card-blur
  reload: catch Exception only and rethrow CancellationException so a viewport
  restart cannot overwrite the newer effect's result with a stale one, and an
  OOM from the StackBlur buffers is not masked as a silent fallback.
- Reset the un-persisted surface-alpha preview when the app is backgrounded
  (Home/recents mid-drag never fires onValueChangeFinished), and re-check the
  dirty flag after the DataStore read to avoid stomping a newer drag.
- Clear the blur composition locals under the opaque OOBE overlay so its cards
  render opaque instead of showing a frosted backdrop.
- Only draw the background dim overlay when a wallpaper painter is present.
- Make the master blur switch description match its new master-toggle role.

@xingguangcuicanbot xingguangcuicanbot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated PR Review

Summary

No actionable issues found in the PR diff. Static review and diff checks passed; Kotlin compilation could not be run because the repository has no gradle wrapper at ./gradlew.

Decision

request_changes

Notes

Codex recommendation: approve
This PR has 1826 changed lines, so policy requires request changes.

@xingguangcuicanbot xingguangcuicanbot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated PR Review

Summary

The shared background painter can regress memory usage by loading full-resolution custom images instead of sizing requests to the screen.

Decision

request_changes

Notes

Codex recommendation: request_changes
Posted 1 inline review comment(s).

Comment thread app/src/main/java/com/abk/kernel/ui/components/AppBackgroundHost.kt
Address the review comment on the shared rememberAsyncImagePainter: instead of
relying on the implicit draw-time viewport sizing, build the painter's request
from an ImageRequest explicitly sized to this host's onSizeChanged viewport, so
a large user wallpaper is decoded at screen resolution rather than its original
dimensions. The request is cached on (uri, viewportSize) so it is rebuilt only
when the wallpaper or the viewport changes, and the painter stays shared across
the visible image and every blur backdrop (no N+1 decode).

@xingguangcuicanbot xingguangcuicanbot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated PR Review

Summary

Found one UI regression: the SUSFS settings page does not enable the new top-bar blur. Gradle verification could not run because this checkout does not contain a Gradle wrapper.

Decision

request_changes

Notes

Codex recommendation: request_changes
Posted 1 inline review comment(s).

@@ -583,9 +607,9 @@

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Location: app/src/main/java/com/abk/kernel/ui/screens/SettingsScreen.kt:642

Issue: The SUSFS child page was migrated to BlurScreenScaffold, but its ExpressiveTopBar does not pass enableBlur = state.blurEnabled, unlike the other migrated screens. As a result, enabling Blur leaves this page's top bar opaque and inconsistent with the rest of the UI.

Suggestion: Pass enableBlur = state.blurEnabled to the SUSFS page's ExpressiveTopBar.

The SUSFS child page was migrated to BlurScreenScaffold but its ExpressiveTopBar
did not forward enableBlur, so its top bar stayed opaque while every other
migrated page frosted its bar. Add enableBlur = state.blurEnabled for
consistency across the UI.
Opening the soft keyboard resizes the window (the bottom bar rides above the
IME), which recomposed the wallpaper request and restarted the card-blur
loader. Both flashed to an empty/opaque state before reloading:

- AppBackgroundHost: size the shared background painter's request to the stable
  full-screen resolution (resources.displayMetrics) instead of the onSizeChanged
  viewport, so a transient IME inset change no longer re-decodes the wallpaper
  (which blanked the background while it reloaded). Full-screen is always >= any
  window viewport, so the crop stays correct.
- rememberBlurredCardBackground: keep the previous frosted backdrop while the
  replacement decodes + StackBlurs, and only swap it in once ready, instead of
  nulling it first (which flashed every card to an opaque/transparent surface).
Port ReSukiSU's approach for the software card-blur backdrop: instead of a
Composable-remembered bitmap that is re-decoded and re-blurred whenever the
viewport changes, keep the pre-blurred wallpaper in a module-level cache and
persist it to filesDir. Combined with the existing keep-until-replaced swap,
transient viewport changes (e.g. the soft keyboard resizing the window on
adjustResize) no longer flash cards opaque or re-run the full decode +
StackBlur pass; matching viewports are restored from disk on the next launch /
re-entry.
Port ReSukiSU's approach to avoiding the visible blur-loading flash: its card
alpha is a synchronous config that only depends on the custom background being
enabled, not on the async pre-blurred bitmap being ready.

Add a synchronous LocalBlurredCardBackgroundEnabled signal (feature on, bitmap
may still be decoding) and use it in blurredCardSurfaceColor and
ExpressiveListItem's drawsOwnBlurSurface instead of checking
LocalBlurredCardBackground != null. Surfaces are now translucent from the very
first frame and only gain the frosted backdrop when it lands, so opening the
app no longer flashes cards from opaque to translucent while the wallpaper
decode + StackBlur runs. OOBE clears the new signal too.
The soft keyboard opening used to resize the window (adjustResize), which shrank
the AppBackgroundHost layer and re-ran the frosted-backdrop blur for every
show/hide. Switch to adjustNothing so the window (and backdrop layer) keeps its
size and the blur is generated once per real size change:

- Manifest: adjustNothing on MainActivity and AbkExtensionManagerActivity.
- AppBackgroundHost: wrap app content in Modifier.imePadding() so content is
  lifted over the keyboard while the wallpaper/backdrop layer stays put.
- API 26-29 do not deliver full IME insets to imePadding, so those devices fall
  back to adjustResize at runtime (Android 10 and below).

Compose Dialog windows are unaffected (they keep their own UNSPECIFIED
soft-input behavior).

@xingguangcuicanbot xingguangcuicanbot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated PR Review

Summary

The on-disk blurred-background cache is unconditionally invalidated at startup, so its intended cross-launch reuse never occurs.

Decision

request_changes

Notes

Codex recommendation: request_changes
Posted 1 inline review comment(s).

Comment thread app/src/main/java/com/abk/kernel/ui/blur/BlurredCardBackdrop.kt Outdated
fanziyun and others added 3 commits August 13, 2026 01:27
Co-authored-by: xingguangcuican_bot <xingguangcuican821@gmail.com>
The bot's cache fix (f8011d6) left the old delete call in place, so the
pre-blurred card cache was still deleted before being read on every launch.
Key the cache file by uri.hashCode() so a stale wallpaper's blur is never
reused across launches, and only delete the old cache on a real in-process
wallpaper switch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ain thread

- Mirror the background anchor's size into snapshot state. LayoutCoordinates.size
  is not observable, so split-screen / freeform / IME resize never re-ran the
  blur and cards drew a stale viewport stretched across the new size.
- Run saveBlurCache (JPEG compress + disk write) on Dispatchers.IO instead of
  the main thread.
- Stop resetting the nested 'render background into blur' pref when the master
  blur switch is toggled off, so the user's setting is preserved.

@xingguangcuicanbot xingguangcuicanbot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated PR Review

Summary

The PR has two actionable cache issues: URI hash collisions can display the wrong blurred background, and URI changes unnecessarily delete reusable cache entries. The Gradle unit-test command could not be run because this checkout has no gradlew wrapper.

Decision

request_changes

Notes

Codex recommendation: request_changes
Posted 2 inline review comment(s).

Comment thread app/src/main/java/com/abk/kernel/ui/blur/BlurredCardBackdrop.kt Outdated
Comment thread app/src/main/java/com/abk/kernel/ui/blur/BlurredCardBackdrop.kt Outdated
… switches

- Key the on-disk cache by SHA-256(uri) instead of String.hashCode(), so two
  distinct background URIs cannot collide into the same file and serve the
  wrong wallpaper's blur.
- Stop deleting the previous URI's cache on wallpaper change; switching back
  now reuses it. Bound disk usage with a count-based prune (keep 4 newest).
- Include the blur version/radius/downsample in the filename so a future
  algorithm change cannot silently reuse an incompatible cache.
…avior

- Revert windowSoftInputMode to adjustResize and drop the now-redundant
  imePadding(): adjustNothing without edge-to-edge never delivered IME insets,
  so the keyboard covered content on Android 11+.
- Key the card-blur re-blur on width (not height) so a height-only IME resize
  no longer re-runs the decode + StackBlur pass.
- Skip drawing the blurred strip while the cached blur's width is stale
  (rotation / split-screen), instead of stretching the old-viewport bitmap.
- Prune orphaned .tmp cache files and delete corrupt cache files on decode
  failure.

@xingguangcuicanbot xingguangcuicanbot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated PR Review

Summary

The blur integration is generally well-structured, but I found two correctness issues in the new card-backdrop pipeline that can leave the UI showing the wrong blurred image under common resize/background-change races.

Decision

request_changes

Notes

Codex recommendation: request_changes
Posted 2 inline review comment(s).

Comment thread app/src/main/java/com/abk/kernel/ui/blur/BlurredCardBackdrop.kt Outdated
Comment thread app/src/main/java/com/abk/kernel/ui/blur/BlurredCardBackdrop.kt
The width-only key avoided IME reloads but also missed real height growth
(freeform taller, fold unfold). Key on both dimensions and skip the expensive
pass only while the cached bitmap still covers the new viewport: growth past
the cached size re-blurs, shrink-only changes (IME) keep the taller bitmap
which still samples the correct region.
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