-
Notifications
You must be signed in to change notification settings - Fork 7
Seven fixes: double tap, the dock over Today View, icons after a theme change, the wallpaper smear, the Preview bar, a way back in Settings, and a cheaper background #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6315322
ed7e75b
00b64f3
8924f8e
454e7e3
71688d8
6036c24
8cb8344
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| package com.mccal.folio | ||
|
|
||
| import androidx.core.content.edit | ||
| import kotlinx.coroutines.flow.conflate | ||
|
|
||
| import android.service.wallpaper.WallpaperService | ||
|
|
@@ -31,6 +32,7 @@ import androidx.compose.ui.geometry.Size | |
| import androidx.compose.ui.graphics.Brush | ||
| import androidx.compose.ui.graphics.Color | ||
| import androidx.compose.ui.graphics.Path | ||
| import androidx.compose.ui.graphics.graphicsLayer | ||
| import androidx.compose.ui.graphics.drawscope.CanvasDrawScope | ||
| import androidx.compose.ui.graphics.drawscope.DrawScope | ||
| import androidx.compose.ui.unit.Density | ||
|
|
@@ -45,7 +47,12 @@ internal fun DuneWallpaper(modifier: Modifier = Modifier) { | |
| val photo = produceState(initialValue = initial, key1 = context, key2 = revision) { | ||
| value = withContext(Dispatchers.IO) { loadLauncherBackground(context) } | ||
| }.value | ||
| Canvas(Modifier.fillMaxSize().then(modifier)) { drawLauncherBackground(photo?.asImageBitmap(), palette.dark) } | ||
| // Drawn once into its own layer and reused: the background never moves, but during a swipe everything above it | ||
| // does, so it was redrawn every frame (a gradient, three dunes and 29 strokes, full screen) and the GPU missed | ||
| // frames. A cached layer is one texture copy a frame instead. | ||
| Canvas(Modifier.fillMaxSize().then(modifier).graphicsLayer { | ||
| compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen | ||
| }) { drawLauncherBackground(photo?.asImageBitmap(), palette.dark) } | ||
| } | ||
|
|
||
| internal fun DrawScope.drawLauncherBackground(photo: ImageBitmap?, dark: Boolean = false) { | ||
|
|
@@ -235,7 +242,7 @@ internal fun SystemWallpaperParallax(pager: androidx.compose.foundation.pager.Pa | |
| * | ||
| * Turning it on needs the window itself, not just a flag on it: `android:windowShowWallpaper` belongs to the theme | ||
| * the window was built from, and a window built opaque shows the wallpaper only through the relayout before covering | ||
| * it again, which looked like the wallpaper flashing up and vanishing. The setting is saved before this is called and | ||
| * it again, which looked like the wallpaper flashing up and vanishing. The setting is saved here, at once, and | ||
| * [MainActivity] picks the wallpaper theme from it, so starting the screen again is what actually turns it on. | ||
| * | ||
| * Turning it off needs no restart: an opaque background over the same window hides the wallpaper, as it always did. | ||
|
|
@@ -251,17 +258,39 @@ internal tailrec fun android.content.Context.asActivity(): android.app.Activity? | |
| } | ||
|
|
||
| internal fun android.app.Activity.applyWallpaperWindow(system: Boolean) { | ||
| val showing = window.attributes.flags and android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER != 0 | ||
| if (system && !showing) { recreate(); return } | ||
| // Saved here and on its own before the screen starts again: the full state save can be held back (a first run | ||
| // still loading its apps), and the new window is built from what's saved. apply() is enough, since the new screen | ||
| // runs in this process and reads the same preferences in memory. | ||
| getSharedPreferences(SettingKeys.PREFS, 0).edit { putBoolean(SettingKeys.SYSTEM_WALLPAPER, system) } | ||
| if (system && !showsWallpaper) { startAgain(); return } | ||
| val background = if (system) android.graphics.drawable.ColorDrawable(android.graphics.Color.TRANSPARENT) | ||
| else obtainStyledAttributes(R.style.Theme_Duo, intArrayOf(android.R.attr.windowBackground)).let { it.getDrawable(0).also { _ -> it.recycle() } } | ||
| window.setBackgroundDrawable(background) | ||
| if (system) window.addFlags(android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) | ||
| else window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) | ||
| } | ||
|
|
||
| /** | ||
| * A new instance of this screen, with a new window. Not `recreate()`: Android keeps the old window across it, and a | ||
| * window first made opaque stays opaque to the screen even after it asks for the wallpaper, so the wallpaper never | ||
| * showed and every swipe piled onto the last frame (#12, #35). The same intent, so Settings opens again on top. | ||
| */ | ||
| private fun android.app.Activity.startAgain() { | ||
| startActivity(android.content.Intent(intent).addFlags( | ||
| android.content.Intent.FLAG_ACTIVITY_NEW_TASK or android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK)) | ||
|
Comment on lines
+279
to
+280
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Android wallpaper is enabled from Settings opened through Home's in-app Customize path, the activity's intent is still the ordinary HOME intent; the open sheet and Useful? React with 👍 / 👎. |
||
| finish() | ||
| } | ||
|
|
||
| /** Whether this window actually shows Android's wallpaper behind it, whatever the setting says. */ | ||
| internal val android.app.Activity.showsWallpaper: Boolean | ||
| get() = window.attributes.flags and android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER != 0 | ||
|
|
||
| /** Whether Home shows Android's wallpaper (read straight from saved state: needed before the activity's window exists). */ | ||
| internal fun usesSystemWallpaper(context: android.content.Context): Boolean = runCatching { | ||
| org.json.JSONObject(context.getSharedPreferences(SettingKeys.PREFS, 0).getString(SettingKeys.STATE, "{}") ?: "{}") | ||
| .optBoolean("systemWallpaper", false) | ||
| val prefs = context.getSharedPreferences(SettingKeys.PREFS, 0) | ||
| if (prefs.contains(SettingKeys.SYSTEM_WALLPAPER)) return@runCatching prefs.getBoolean(SettingKeys.SYSTEM_WALLPAPER, false) | ||
| org.json.JSONObject(prefs.getString(SettingKeys.STATE, "{}") ?: "{}").optBoolean("systemWallpaper", false) | ||
| }.getOrDefault(false) | ||
|
|
||
| /** Once per process: the window is rebuilt at most once to match the setting, so a mismatch can never loop. */ | ||
| internal object WallpaperWindowRepair { var tried = false } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -338,6 +338,8 @@ class LauncherModel(application: Application) : AndroidViewModel(application) { | |
| // Accessed only in the serialized IO refresh. Returning Home reuses existing bitmaps. | ||
| private val iconCache = mutableMapOf<String, AppEntry>() | ||
| private var iconConfiguration = "" | ||
| /** Set by [reloadIcons]; the refresh that follows drops every cached icon. */ | ||
| @Volatile private var iconsStale = false | ||
| internal var completedRefreshes = 0 | ||
| private set | ||
| private val callback = object : LauncherApps.Callback() { | ||
|
|
@@ -375,6 +377,9 @@ class LauncherModel(application: Application) : AndroidViewModel(application) { | |
|
|
||
| init { launcherApps.registerCallback(callback); refresh(); FolioSettingsBridge.liveModel = java.lang.ref.WeakReference(this) } | ||
|
|
||
| /** Settings › Refresh Icons: for a theme app that changes icons without telling launchers (#19). */ | ||
| fun reloadIcons() { iconsStale = true; IconPacks.clear(); refresh() } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an icon pack is active, this clears Useful? React with 👍 / 👎. |
||
|
|
||
| fun refresh(invalidatedPackage: String? = null, user: UserHandle = Process.myUserHandle()) { | ||
| invalidatedPackage?.let { invalidatedPackages += userManager.getSerialNumberForUser(user) to it } | ||
| if (refreshing) { refreshPending = true; return } | ||
|
|
@@ -385,11 +390,11 @@ class LauncherModel(application: Application) : AndroidViewModel(application) { | |
| invalidatedPackages.clear() | ||
| removedPackages.clear() | ||
| val resources = getApplication<Application>().resources | ||
| val configuration = resources.configuration.let { "${it.densityDpi}|${it.locales.toLanguageTags()}|${it.uiMode}" } | ||
| val configuration = resources.configuration.let { "${it.densityDpi}|${it.locales.toLanguageTags()}|${it.uiMode}|${assetsSequence(it.toString())}" } | ||
| viewModelScope.launch { | ||
| try { | ||
| val apps = withContext(Dispatchers.IO) { | ||
| if (configuration != iconConfiguration) { iconCache.clear(); iconConfiguration = configuration } | ||
| if (configuration != iconConfiguration || iconsStale) { iconCache.clear(); iconConfiguration = configuration; iconsStale = false } | ||
| iconCache.keys.removeAll { key -> parseProfileAppId(key)?.let { identity -> | ||
| val serial = identity.userSerial ?: userManager.getSerialNumberForUser(Process.myUserHandle()) | ||
| serial to (ComponentName.unflattenFromString(identity.component)?.packageName ?: "") in invalidated | ||
|
|
@@ -1146,7 +1151,9 @@ class LauncherModel(application: Application) : AndroidViewModel(application) { | |
| editor.putString("state_v7_backup", legacyRaw) | ||
| if (legacyRaw != null && sourceSchema < 9 && !prefs.contains("state_v8_backup")) | ||
| editor.putString("state_v8_backup", legacyRaw) | ||
| editor.putString("state", data.toString()).putBoolean("initialized", true).apply() | ||
| editor.putString("state", data.toString()).putBoolean("initialized", true) | ||
| // Kept beside the state so a restore or theme import that changes it chooses the right window next time. | ||
| .putBoolean(SettingKeys.SYSTEM_WALLPAPER, s.systemWallpaper).apply() | ||
| } | ||
|
|
||
| private fun load(): LauncherState = runCatching { | ||
|
|
@@ -1428,3 +1435,11 @@ val LauncherState.glassTintAmount: Float get() = if (tintedGlass) .56f * glassTi | |
| /** Reduce Transparency: nearly solid widgets, Side Bar and dock, with a clearer edge (the saved values stay as they are). */ | ||
| fun LauncherState.withSolidGlass(): LauncherState = | ||
| copy(widgetGlass = maxOf(widgetGlass, .9f), glassOutline = maxOf(glassOutline, .45f), statusStyle = statusStyle.copy(railGlass = maxOf(statusStyle.railGlass, .9f))) | ||
|
|
||
| /** | ||
| * Android counts changes to installed themes and overlays (Samsung's Theme Park among them) in the configuration's | ||
| * assets sequence. It isn't public API, but the configuration prints it as "as.N", so a theme that swaps icons without | ||
| * a package change still clears the icon cache, while folding and rotating don't. 0 when it isn't there. | ||
| */ | ||
| internal fun assetsSequence(configuration: String): Int = | ||
| Regex("""\bas\.(\d+)""").find(configuration)?.groupValues?.get(1)?.toIntOrNull() ?: 0 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the user selects Android wallpaper while the initial app refresh/migration is still running,
LauncherModel.setSystemWallpaper(true)cannot persist the JSON state becausepersist()returns whileneedsMigrationis true. This restart then destroys that model; the replacement model decodessystemWallpaper=falsefrom the unchanged JSON, soDuneWallpapercovers the newly themed window and the next refresh writesfalseback over this standalone preference. The separately saved value must also seed the replacementLauncherState, or the restart must wait until the state itself is durable.Useful? React with 👍 / 👎.