Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c6c8903
feat: port miuix blur to expressive UI
fanziyun Aug 11, 2026
a7e0439
refactor(blur): clean up comments and unused properties
fanziyun Aug 11, 2026
ba22618
Update app/src/main/java/com/abk/kernel/ui/blur/BlurredCardBackdrop.kt
fanziyun Aug 11, 2026
413263b
Update app/src/main/java/com/abk/kernel/ui/components/AppBackgroundHo…
fanziyun Aug 11, 2026
f5f56a8
Update app/src/main/java/com/abk/kernel/ui/blur/BlurredCardBackdrop.kt
fanziyun Aug 11, 2026
b865e99
fix(blur): repair broken Kotlin syntax from PR edits
fanziyun Aug 11, 2026
d640a79
fix(blur): address multi-agent review findings
fanziyun Aug 12, 2026
a849703
fix(blur): restore ContentScale.Crop on the shared background image
fanziyun Aug 12, 2026
63ff616
fix(blur): harden blur gating, cancellation, and toggle semantics
fanziyun Aug 12, 2026
fb7a293
fix(blur): bound the shared background painter's decode to the viewport
fanziyun Aug 12, 2026
8a7161c
fix(blur): pass enableBlur to the SUSFS page top bar
fanziyun Aug 12, 2026
679eeba
fix(blur): stop background/card blur from flashing on IME-driven resizes
fanziyun Aug 12, 2026
23dd907
fix(blur): cache the pre-blurred card background globally and on disk
fanziyun Aug 12, 2026
7e138fb
fix(blur): keep card surfaces translucent from the first frame
fanziyun Aug 12, 2026
6025546
fix(blur): stop IME-driven resizes from re-blurring the backdrop
fanziyun Aug 12, 2026
f8011d6
Update app/src/main/java/com/abk/kernel/ui/blur/BlurredCardBackdrop.kt
fanziyun Aug 12, 2026
52a51c2
fix(blur): key on-disk blur cache by URI and stop deleting it on startup
fanziyun Aug 12, 2026
6f86030
fix(blur): refresh backdrop on real resize and move cache write off m…
fanziyun Aug 12, 2026
318e86e
fix(blur): use SHA-256 cache keys and keep per-URI blur caches across…
fanziyun Aug 12, 2026
f6bacb8
fix(ui): restore adjustResize and harden blur resize + disk-cache beh…
fanziyun Aug 12, 2026
4e2c829
fix(blur): re-blur on real viewport growth while skipping IME shrink
fanziyun Aug 13, 2026
c1f67bb
chore(blur): trim redundant comments
fanziyun Aug 13, 2026
82f4a3d
fix(blur): reuse shared wallpaper painter to stop black flash
fanziyun Aug 13, 2026
3f098a6
refactor(ui): merge blur card into background settings
fanziyun Aug 13, 2026
2900e5d
fix(blur): load wallpaper and frosted backdrop instantly on cold start
fanziyun Aug 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ dependencies {
// Preferences
implementation(libs.datastore.preferences)

// Blur / glass
implementation(libs.miuix.blur)

testImplementation(libs.junit)
testImplementation(libs.robolectric)
testImplementation(libs.kotlinx.coroutines.test)
Expand Down
14 changes: 13 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Expand All @@ -14,6 +15,16 @@
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

<!--
The miuix-blur AAR declares minSdk 33 while this app supports API 26+. The override
keeps the build working; safety is enforced at runtime. Every path into the library
(rememberBlurBackdrop, blurSource, blurEffect, blurSourceBody, isBlurActive) is gated
on isRuntimeShaderSupported() (API 33+), and the AAR registers no ContentProvider or
initializer, so nothing library-side executes on API 26-32. Keep these guards intact
if the dependency version or call sites change.
-->
<uses-sdk tools:overrideLibrary="top.yukonga.miuix.kmp.blur" />

<application
android:name=".AbkApplication"
android:allowBackup="true"
Expand Down Expand Up @@ -80,6 +91,7 @@
<activity
android:name=".extensions.AbkExtensionManagerActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.ABK" />

<service
Expand Down
71 changes: 65 additions & 6 deletions app/src/main/java/com/abk/kernel/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalConfiguration
Expand All @@ -89,6 +90,15 @@ import com.abk.kernel.ui.components.AbkSnackbarHost
import com.abk.kernel.ui.components.animateBottomNavForChildPage
import com.abk.kernel.ui.components.showAbkSnackbar
import com.abk.kernel.extensions.AbkExtensionBootstrapActivity
import com.abk.kernel.ui.blur.LocalBlurBackgroundAnchor
import com.abk.kernel.ui.blur.LocalBlurState
import com.abk.kernel.ui.blur.LocalBlurredCardBackground
import com.abk.kernel.ui.blur.LocalBlurredCardBackgroundEnabled
import com.abk.kernel.ui.blur.blurEffect
import com.abk.kernel.ui.blur.blurSourceBody
import com.abk.kernel.ui.blur.isBlurActive
import com.abk.kernel.ui.blur.rememberBlurBackdrop
import com.abk.kernel.ui.blur.rememberBlurBackgroundPainter
import com.abk.kernel.ui.screens.BuildScreen
import com.abk.kernel.ui.screens.FlashScreen
import com.abk.kernel.ui.screens.InstalledModulesScreen
Expand Down Expand Up @@ -118,11 +128,15 @@ class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// adjustResize keeps content above the IME; the card-blur backdrop keys its
// re-blur on width (not height), so a height-only IME resize does not re-run the
// decode + StackBlur pass.
pendingModuleInstallUri = extractModuleInstallUri(intent)?.toString()

setContent {
val vm: MainViewModel = viewModel()
val state by vm.uiState.collectAsState()
val uiSurfaceAlphaPreview by vm.uiSurfaceAlphaPreview.collectAsState(initial = state.uiSurfaceAlpha)
var extensionBootstrapIssued by rememberSaveable { mutableStateOf(false) }

LaunchedEffect(Unit) {
Expand Down Expand Up @@ -161,7 +175,8 @@ class MainActivity : ComponentActivity() {
AppBackgroundHost(
backgroundUri = state.customBackgroundUri,
backgroundEnabled = state.backgroundImageEnabled,
uiSurfaceAlpha = state.uiSurfaceAlpha
uiSurfaceAlpha = uiSurfaceAlphaPreview,
blurBackgroundEnabled = state.blurConfig.wantsBackgroundPainter,
) {
when {
!state.termsLoaded -> Surface(
Expand Down Expand Up @@ -198,7 +213,15 @@ class MainActivity : ComponentActivity() {
}
}
if (state.showOobe) {
CompositionLocalProvider(LocalUiSurfaceAlpha provides 1f) {
// OOBE is an opaque onboarding screen; clear the blur
// locals so its cards render opaque instead of showing a
// translucent frosted backdrop under the wallpaper.
CompositionLocalProvider(
LocalUiSurfaceAlpha provides 1f,
LocalBlurredCardBackground provides null,
LocalBlurredCardBackgroundEnabled provides false,
LocalBlurBackgroundAnchor provides null,
) {
Box(
modifier = Modifier
.fillMaxSize()
Expand Down Expand Up @@ -582,6 +605,29 @@ private fun AbkMainScaffold(
}
val navProgress = navProgressAnim.value

// Bottom-nav progress goes 1f (bar shown) → 0f (a child page slides the bar off).
// Only run the bar backdrop and its blur pipeline while the bar is actually on
// screen; once it is fully hidden (matches ChildPageMotion's hide epsilon) or the
// opaque OOBE overlay covers everything, every recordLayer + blur pass is invisible,
// so it is switched off.
val barBlurOnScreen = !state.showOobe && navProgress > 0.02f
val blurBackdrop = rememberBlurBackdrop(
enableBlur = state.blurConfig.blurEnabled && barBlurOnScreen,
surfaceColor = MaterialTheme.colorScheme.surfaceContainer,
backgroundPainter = if (barBlurOnScreen) {
rememberBlurBackgroundPainter(state.blurConfig)
} else {
null
},
)

CompositionLocalProvider(
LocalBlurState provides blurBackdrop,
) {
// Gate bar transparency on the frosted effect actually rendering (API >= 33),
// so pre-Android-13 devices fall back to the opaque surface color.
val blurActive = isBlurActive(state.blurEnabled && barBlurOnScreen)

Box(
modifier = Modifier
.fillMaxSize()
Expand All @@ -603,8 +649,14 @@ private fun AbkMainScaffold(
contentAlignment = Alignment.Center
) {
NavigationRail(
modifier = Modifier.fillMaxSize(),
containerColor = uiSurfaceColor(MaterialTheme.colorScheme.surfaceContainer)
modifier = Modifier
.fillMaxSize()
.then(if (blurActive) Modifier.blurEffect() else Modifier),
containerColor = if (blurActive) {
Color.Transparent
} else {
uiSurfaceColor(MaterialTheme.colorScheme.surfaceContainer)
}
) {
visibleTabs.forEach { tab ->
NavigationRailItem(
Expand Down Expand Up @@ -650,8 +702,13 @@ private fun AbkMainScaffold(
val hidden = 1f - navProgress
translationY = hidden * bottomBarHeightPx
alpha = 1f - (hidden * 0.15f)
},
containerColor = uiSurfaceColor(MaterialTheme.colorScheme.surfaceContainer),
}
.then(if (blurActive) Modifier.blurEffect() else Modifier),
containerColor = if (blurActive) {
Color.Transparent
} else {
uiSurfaceColor(MaterialTheme.colorScheme.surfaceContainer)
},
tonalElevation = 0.dp
) {
visibleTabs.forEach { tab ->
Expand Down Expand Up @@ -691,6 +748,7 @@ private fun AbkMainScaffold(
modifier = Modifier
.fillMaxSize()
.zIndex(1f)
.blurSourceBody()
) {
Box(
modifier = Modifier
Expand Down Expand Up @@ -789,6 +847,7 @@ private fun AbkMainScaffold(
)
}
}
}

@Composable
private fun AbkTab.displayLabel(rootGranted: Boolean): String = when (this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.abk.kernel.data.model.normalizeAppUpdateStability
import com.abk.kernel.utils.DownloadDirectoryUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
Expand Down Expand Up @@ -53,6 +54,8 @@ class PreferencesRepository(private val context: Context) {
val KEY_CUSTOM_BACKGROUND_URI = stringPreferencesKey("custom_background_uri")
val KEY_BACKGROUND_IMAGE_ENABLED = booleanPreferencesKey("background_image_enabled")
val KEY_UI_SURFACE_ALPHA = floatPreferencesKey("ui_surface_alpha")
val KEY_BLUR_ENABLED = booleanPreferencesKey("blur_enabled")
val KEY_BLUR_BACKGROUND_EXP_ENABLED = booleanPreferencesKey("blur_background_exp_enabled")
val KEY_BUILD_CONFIG = stringPreferencesKey("build_config_json")
val KEY_BUILD_PLANS = stringPreferencesKey("build_plans_json")
val KEY_BUILD_QUEUE = stringPreferencesKey("build_queue_json")
Expand Down Expand Up @@ -105,9 +108,16 @@ class PreferencesRepository(private val context: Context) {
val dynamicColorEnabled: Flow<Boolean> = context.dataStore.data.map { it[KEY_DYNAMIC_COLOR_ENABLED] ?: true }
val customThemeColorArgb: Flow<Int?> = context.dataStore.data.map { it[KEY_CUSTOM_THEME_COLOR] }
val customAccentColorArgb: Flow<Int?> = context.dataStore.data.map { it[KEY_CUSTOM_ACCENT_COLOR] }
val customBackgroundUri: Flow<String?> = context.dataStore.data.map { it[KEY_CUSTOM_BACKGROUND_URI] }
val backgroundImageEnabled: Flow<Boolean> = context.dataStore.data.map { it[KEY_BACKGROUND_IMAGE_ENABLED] ?: false }
val uiSurfaceAlpha: Flow<Float> = context.dataStore.data.map { it[KEY_UI_SURFACE_ALPHA] ?: 1f }
val customBackgroundUri: Flow<String?> =
context.dataStore.data.map { it[KEY_CUSTOM_BACKGROUND_URI] }.distinctUntilChanged()
val backgroundImageEnabled: Flow<Boolean> =
context.dataStore.data.map { it[KEY_BACKGROUND_IMAGE_ENABLED] ?: false }.distinctUntilChanged()
val uiSurfaceAlpha: Flow<Float> =
context.dataStore.data.map { it[KEY_UI_SURFACE_ALPHA] ?: 1f }.distinctUntilChanged()
val blurEnabled: Flow<Boolean> =
context.dataStore.data.map { it[KEY_BLUR_ENABLED] ?: true }.distinctUntilChanged()
val blurBackgroundExpEnabled: Flow<Boolean> =
context.dataStore.data.map { it[KEY_BLUR_BACKGROUND_EXP_ENABLED] ?: false }.distinctUntilChanged()
val buildConfigJson: Flow<String?> = context.dataStore.data.map { it[KEY_BUILD_CONFIG] }
val buildPlansJson: Flow<String?> = context.dataStore.data.map { it[KEY_BUILD_PLANS] }
val buildQueueJson: Flow<String?> = context.dataStore.data.map { it[KEY_BUILD_QUEUE] }
Expand Down Expand Up @@ -247,6 +257,12 @@ class PreferencesRepository(private val context: Context) {
suspend fun setUiSurfaceAlpha(alpha: Float) = context.dataStore.edit {
it[KEY_UI_SURFACE_ALPHA] = alpha.coerceIn(0f, 1f)
}
suspend fun setBlurEnabled(v: Boolean) = context.dataStore.edit {
it[KEY_BLUR_ENABLED] = v
}
suspend fun setBlurBackgroundExpEnabled(v: Boolean) = context.dataStore.edit {
it[KEY_BLUR_BACKGROUND_EXP_ENABLED] = v
}
suspend fun saveBuildConfigJson(json: String) = context.dataStore.edit { it[KEY_BUILD_CONFIG] = json }
suspend fun saveBuildPlansJson(json: String) = context.dataStore.edit { it[KEY_BUILD_PLANS] = json }
suspend fun saveBuildQueueJson(json: String) = context.dataStore.edit { it[KEY_BUILD_QUEUE] = json }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
Expand All @@ -51,12 +50,15 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import com.abk.kernel.R
import com.abk.kernel.data.repository.PreferencesRepository
import com.abk.kernel.ui.blur.BlurConfig
import com.abk.kernel.ui.blur.BlurScreenScaffold
import com.abk.kernel.ui.components.AbkCenteredLoadingTransition
import com.abk.kernel.ui.components.AbkScreenHorizontalPadding
import com.abk.kernel.ui.components.AppBackgroundHost
Expand Down Expand Up @@ -91,6 +93,21 @@ class AbkExtensionManagerActivity : ComponentActivity() {
val customBackgroundUri by prefs.customBackgroundUri.collectAsState(initial = null)
val backgroundImageEnabled by prefs.backgroundImageEnabled.collectAsState(initial = false)
val uiSurfaceAlpha by prefs.uiSurfaceAlpha.collectAsState(initial = 1f)
val blurEnabled by prefs.blurEnabled.collectAsState(initial = true)
val blurBackgroundExpEnabled by prefs.blurBackgroundExpEnabled.collectAsState(initial = false)
val blurConfig = remember(
customBackgroundUri,
backgroundImageEnabled,
blurEnabled,
blurBackgroundExpEnabled
) {
BlurConfig(
blurEnabled = blurEnabled,
backgroundExpEnabled = blurBackgroundExpEnabled,
backgroundUri = customBackgroundUri,
backgroundImageEnabled = backgroundImageEnabled,
)
}

AbkTheme(
themeMode = themeMode,
Expand All @@ -101,13 +118,18 @@ class AbkExtensionManagerActivity : ComponentActivity() {
AppBackgroundHost(
backgroundUri = customBackgroundUri,
backgroundEnabled = backgroundImageEnabled,
uiSurfaceAlpha = uiSurfaceAlpha
uiSurfaceAlpha = uiSurfaceAlpha,
blurBackgroundEnabled = blurConfig.wantsBackgroundPainter,
) {
AbkExtensionManagerScreen(
focusExtensionId = focusExtensionId,
bootstrapMode = bootstrapMode,
onBack = ::finish,
onExternalFlowLaunched = ::finish,
blurEnabled = blurEnabled,
blurBackgroundExpEnabled = blurBackgroundExpEnabled,
backgroundUri = customBackgroundUri,
backgroundImageEnabled = backgroundImageEnabled,
)
}
}
Expand All @@ -122,6 +144,10 @@ fun AbkExtensionManagerScreen(
onBack: () -> Unit,
modifier: Modifier = Modifier,
containerColor: Color = Color.Transparent,
blurEnabled: Boolean = false,
blurBackgroundExpEnabled: Boolean = false,
backgroundUri: String? = null,
backgroundImageEnabled: Boolean = false,
onExternalFlowLaunched: () -> Unit = {},
) {
val context = LocalContext.current
Expand Down Expand Up @@ -269,7 +295,13 @@ fun AbkExtensionManagerScreen(
return
}

Scaffold(
BlurScreenScaffold(
blurConfig = BlurConfig(
blurEnabled = blurEnabled,
backgroundExpEnabled = blurBackgroundExpEnabled,
backgroundUri = backgroundUri,
backgroundImageEnabled = backgroundImageEnabled,
),
modifier = modifier,
containerColor = containerColor,
topBar = {
Expand All @@ -293,28 +325,32 @@ fun AbkExtensionManagerScreen(
}
}
},
enableBlur = blurEnabled,
actions = {
IconButton(onClick = ::requestRefresh) {
Icon(Icons.Default.Refresh, contentDescription = stringResource(R.string.refresh))
}
}
)
}
) { padding ->
) { topBarHeight ->
if (loading) {
AbkCenteredLoadingTransition(
text = stringResource(R.string.loading),
modifier = Modifier.padding(padding)
modifier = Modifier
.fillMaxSize()
.padding(top = topBarHeight + 16.dp)
)
return@Scaffold
return@BlurScreenScaffold
}

if (extensions.isEmpty()) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(horizontal = AbkScreenHorizontalPadding, vertical = 16.dp),
.padding(top = topBarHeight + 16.dp)
.padding(horizontal = AbkScreenHorizontalPadding)
.padding(bottom = 16.dp),
verticalArrangement = Arrangement.Top
) {
ExpressiveSectionCard(
Expand All @@ -324,16 +360,14 @@ fun AbkExtensionManagerScreen(
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
) {}
}
return@Scaffold
return@BlurScreenScaffold
}

LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(padding),
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(
start = AbkScreenHorizontalPadding,
top = 16.dp,
top = topBarHeight + 16.dp,
end = AbkScreenHorizontalPadding,
bottom = 80.dp
),
Expand Down
Loading
Loading