Bläddra i källkod

修复: 切换主题时支持无重启更新,优化配置管理

5151561 3 månader sedan
förälder
incheckning
9db2bff558

+ 9 - 0
app/src/main/AndroidManifest.xml

@@ -39,6 +39,7 @@
         <activity
         <activity
             android:name=".ui.main.LauncherW"
             android:name=".ui.main.LauncherW"
             android:enabled="false"
             android:enabled="false"
+            android:configChanges="uiMode"
             android:exported="true"
             android:exported="true"
             android:icon="@mipmap/launcherw">
             android:icon="@mipmap/launcherw">
             <intent-filter>
             <intent-filter>
@@ -54,6 +55,7 @@
         <activity
         <activity
             android:name=".ui.main.Launcher0"
             android:name=".ui.main.Launcher0"
             android:enabled="false"
             android:enabled="false"
+            android:configChanges="uiMode"
             android:exported="true"
             android:exported="true"
             android:icon="@mipmap/launcher0">
             android:icon="@mipmap/launcher0">
             <intent-filter>
             <intent-filter>
@@ -70,6 +72,7 @@
         <activity
         <activity
             android:name=".ui.main.Launcher1"
             android:name=".ui.main.Launcher1"
             android:enabled="false"
             android:enabled="false"
+            android:configChanges="uiMode"
             android:exported="true"
             android:exported="true"
             android:icon="@mipmap/launcher1">
             android:icon="@mipmap/launcher1">
             <intent-filter>
             <intent-filter>
@@ -86,6 +89,7 @@
         <activity
         <activity
             android:name=".ui.main.Launcher2"
             android:name=".ui.main.Launcher2"
             android:enabled="false"
             android:enabled="false"
+            android:configChanges="uiMode"
             android:exported="true"
             android:exported="true"
             android:icon="@mipmap/launcher2">
             android:icon="@mipmap/launcher2">
             <intent-filter>
             <intent-filter>
@@ -102,6 +106,7 @@
         <activity
         <activity
             android:name=".ui.main.Launcher3"
             android:name=".ui.main.Launcher3"
             android:enabled="false"
             android:enabled="false"
+            android:configChanges="uiMode"
             android:exported="true"
             android:exported="true"
             android:icon="@mipmap/launcher3">
             android:icon="@mipmap/launcher3">
             <intent-filter>
             <intent-filter>
@@ -118,6 +123,7 @@
         <activity
         <activity
             android:name=".ui.main.Launcher4"
             android:name=".ui.main.Launcher4"
             android:enabled="false"
             android:enabled="false"
+            android:configChanges="uiMode"
             android:exported="true"
             android:exported="true"
             android:icon="@mipmap/launcher4">
             android:icon="@mipmap/launcher4">
             <intent-filter>
             <intent-filter>
@@ -134,6 +140,7 @@
         <activity
         <activity
             android:name=".ui.main.Launcher5"
             android:name=".ui.main.Launcher5"
             android:enabled="false"
             android:enabled="false"
+            android:configChanges="uiMode"
             android:exported="true"
             android:exported="true"
             android:icon="@mipmap/launcher5">
             android:icon="@mipmap/launcher5">
             <intent-filter>
             <intent-filter>
@@ -150,6 +157,7 @@
         <activity
         <activity
             android:name=".ui.main.Launcher6"
             android:name=".ui.main.Launcher6"
             android:enabled="false"
             android:enabled="false"
+            android:configChanges="uiMode"
             android:exported="true"
             android:exported="true"
             android:icon="@mipmap/launcher6">
             android:icon="@mipmap/launcher6">
             <intent-filter>
             <intent-filter>
@@ -166,6 +174,7 @@
         <activity
         <activity
             android:name=".ui.main.MainActivity"
             android:name=".ui.main.MainActivity"
             android:alwaysRetainTaskState="true"
             android:alwaysRetainTaskState="true"
+            android:configChanges="uiMode"
             android:enableOnBackInvokedCallback="true"
             android:enableOnBackInvokedCallback="true"
             android:windowSoftInputMode="adjustResize"
             android:windowSoftInputMode="adjustResize"
             android:exported="true">
             android:exported="true">

+ 13 - 0
app/src/main/java/io/legado/app/base/BaseComposeActivity.kt

@@ -1,5 +1,6 @@
 package io.legado.app.base
 package io.legado.app.base
 
 
+import android.content.res.Configuration
 import android.os.Bundle
 import android.os.Bundle
 import android.os.Build
 import android.os.Build
 import androidx.activity.compose.setContent
 import androidx.activity.compose.setContent
@@ -56,6 +57,18 @@ abstract class BaseComposeActivity(
         observeLiveBus()
         observeLiveBus()
     }
     }
 
 
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        // manifest 声明 uiMode configChanges 后,切换深浅色不再 recreate,
+        // 而 AppCompat 手动回调本方法时不会走 View 树分发,需要自己同步给
+        // Compose(LocalConfiguration)并刷新系统栏与背景图
+        window.decorView.dispatchConfigurationChanged(newConfig)
+        setupSystemBar()
+        if (imageBg) {
+            upBackgroundImage()
+        }
+    }
+
     open fun setupSystemBar() {
     open fun setupSystemBar() {
         WindowCompat.setDecorFitsSystemWindows(window, false)
         WindowCompat.setDecorFitsSystemWindows(window, false)
 
 

+ 10 - 0
app/src/main/java/io/legado/app/help/config/ThemeConfigStore.kt

@@ -49,6 +49,16 @@ object ThemeConfigStore {
         postEvent(EventBus.UP_CONFIG, arrayListOf(2))
         postEvent(EventBus.UP_CONFIG, arrayListOf(2))
     }
     }
 
 
+    /**
+     * 切换深浅色但不发 RECREATE:Compose 界面通过 ThemeConfig.themeMode 快照状态
+     * 自动换色(保留 ToggleButton 等组件的过渡动画),setDefaultNightMode 只会重建
+     * 未在 manifest 声明 uiMode configChanges 的旧 View Activity
+     */
+    fun applyDayNightLive() {
+        initNightMode()
+        postEvent(EventBus.UP_CONFIG, arrayListOf(2))
+    }
+
     fun applyDayNightInit(context: Context) {
     fun applyDayNightInit(context: Context) {
         initNightMode()
         initNightMode()
     }
     }

+ 8 - 3
app/src/main/java/io/legado/app/ui/config/PrefDelegate.kt

@@ -61,6 +61,10 @@ fun <T> prefDelegate(
         @Volatile
         @Volatile
         private var currentValue: T = defaultValue
         private var currentValue: T = defaultValue
         private val scope = CoroutineScope(kotlinx.coroutines.Dispatchers.IO)
         private val scope = CoroutineScope(kotlinx.coroutines.Dispatchers.IO)
+
+        // 单并发调度器保证同一 key 的 DataStore 写入按提交顺序落盘
+        @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
+        private val dsWriteDispatcher = kotlinx.coroutines.Dispatchers.IO.limitedParallelism(1)
         private var dsObserverJob: Job? = null
         private var dsObserverJob: Job? = null
 
 
         init {
         init {
@@ -131,9 +135,10 @@ fun <T> prefDelegate(
                     is Long -> appCtx.putPrefLong(key, value)
                     is Long -> appCtx.putPrefLong(key, value)
                     is Float -> appCtx.putPrefFloat(key, value)
                     is Float -> appCtx.putPrefFloat(key, value)
                 }
                 }
-                // 同步写入 DataStore,确保持久化后再返回
-                runCatching {
-                    runBlocking {
+                // 异步串行写入 DataStore;runBlocking 会把整文件重写 + fsync
+                // 挂在调用线程(通常是主线程的点击回调)上,造成明显掉帧
+                scope.launch(dsWriteDispatcher) {
+                    runCatching {
                         when (value) {
                         when (value) {
                             is String? -> DsSync.putString(key, value)
                             is String? -> DsSync.putString(key, value)
                             is Int -> DsSync.putInt(key, value)
                             is Int -> DsSync.putInt(key, value)

+ 29 - 20
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt

@@ -234,7 +234,7 @@ fun ThemeConfigScreen(
                             onValueChange = { mode ->
                             onValueChange = { mode ->
                                 selectedThemeMode = mode
                                 selectedThemeMode = mode
                                 ThemeConfig.themeMode = mode
                                 ThemeConfig.themeMode = mode
-                                ThemeConfigStore.applyDayNight(context)
+                                ThemeConfigStore.applyDayNightLive()
                             }
                             }
                         )
                         )
 
 
@@ -274,7 +274,7 @@ fun ThemeConfigScreen(
                             onModeSelected = { mode ->
                             onModeSelected = { mode ->
                                 selectedThemeMode = mode
                                 selectedThemeMode = mode
                                 ThemeConfig.themeMode = mode
                                 ThemeConfig.themeMode = mode
-                                ThemeConfigStore.applyDayNight(context)
+                                ThemeConfigStore.applyDayNightLive()
                             }
                             }
                         )
                         )
                     }
                     }
@@ -975,15 +975,20 @@ fun ThemeColorButton(
     customNightSeedColor: Int,
     customNightSeedColor: Int,
     onClick: () -> Unit
     onClick: () -> Unit
 ) {
 ) {
-    val colors = getThemeColorPalette(
-        context = context,
-        value = value,
-        isDark = isDark,
-        isAmoled = isAmoled,
-        paletteStyle = paletteStyle,
-        customLightSeedColor = customLightSeedColor,
-        customNightSeedColor = customNightSeedColor
-    )
+    // 配色方案由种子色实时生成,开销不小,缓存避免无关重组时重复计算
+    val colors = remember(
+        value, isDark, isAmoled, paletteStyle, customLightSeedColor, customNightSeedColor
+    ) {
+        getThemeColorPalette(
+            context = context,
+            value = value,
+            isDark = isDark,
+            isAmoled = isAmoled,
+            paletteStyle = paletteStyle,
+            customLightSeedColor = customLightSeedColor,
+            customNightSeedColor = customNightSeedColor
+        )
+    }
     val borderWidth by animateDpAsState(
     val borderWidth by animateDpAsState(
         targetValue = if (isSelected) 2.dp else 0.dp,
         targetValue = if (isSelected) 2.dp else 0.dp,
         label = "borderWidth"
         label = "borderWidth"
@@ -1076,15 +1081,19 @@ fun ThemeCard(
     customLightSeedColor: Int,
     customLightSeedColor: Int,
     customNightSeedColor: Int
     customNightSeedColor: Int
 ) {
 ) {
-    val colors = getThemeColors(
-        context = context,
-        value = value,
-        isDark = isDark,
-        isAmoled = isAmoled,
-        paletteStyle = paletteStyle,
-        customLightSeedColor = customLightSeedColor,
-        customNightSeedColor = customNightSeedColor
-    )
+    val colors = remember(
+        value, isDark, isAmoled, paletteStyle, customLightSeedColor, customNightSeedColor
+    ) {
+        getThemeColors(
+            context = context,
+            value = value,
+            isDark = isDark,
+            isAmoled = isAmoled,
+            paletteStyle = paletteStyle,
+            customLightSeedColor = customLightSeedColor,
+            customNightSeedColor = customNightSeedColor
+        )
+    }
 
 
     Column(
     Column(
         horizontalAlignment = Alignment.CenterHorizontally
         horizontalAlignment = Alignment.CenterHorizontally

+ 6 - 2
app/src/main/java/io/legado/app/ui/theme/AppTheme.kt

@@ -131,12 +131,16 @@ private fun AppThemeActual(
     }
     }
 
 
     // 6. 构造 Legado 主题模式数据
     // 6. 构造 Legado 主题模式数据
+    // themeMode 用 effectiveDarkTheme 归一化(System 已在上面解析成明确的深浅色),
+    // 这样"跟随系统"和"深色"在系统深色下产生相等的 LegadoThemeMode,
+    // staticCompositionLocalOf 不会触发全树重组
     val themeColors = remember(
     val themeColors = remember(
         colorScheme, effectiveDarkTheme, themeSeedColor, paletteStyleValue, composeEngine,
         colorScheme, effectiveDarkTheme, themeSeedColor, paletteStyleValue, composeEngine,
-        appThemeMode, themeModeValue
+        appThemeMode
     ) {
     ) {
         val paletteStyle = ThemeResolver.resolvePaletteStyle(paletteStyleValue)
         val paletteStyle = ThemeResolver.resolvePaletteStyle(paletteStyleValue)
-        val colorSchemeMode = ThemeResolver.resolveColorSchemeMode(themeModeValue)
+        val colorSchemeMode =
+            if (effectiveDarkTheme) ColorSchemeMode.Dark else ColorSchemeMode.Light
         LegadoThemeMode(
         LegadoThemeMode(
             colorScheme = colorScheme,
             colorScheme = colorScheme,
             isDark = effectiveDarkTheme,
             isDark = effectiveDarkTheme,

+ 2 - 1
app/src/main/java/io/legado/app/ui/widget/components/FontSelectSheet.kt

@@ -80,7 +80,8 @@ fun FontSelectSheet(
             SmallPlainButton(
             SmallPlainButton(
                 onClick = onOpenFolderPicker,
                 onClick = onOpenFolderPicker,
                 icon = folderIcon,
                 icon = folderIcon,
-                contentDescription = folderContentDescription,
+                contentDescription = folderContentDescription
+                    ?: stringResource(R.string.select_folder),
             )
             )
         },
         },
     ) {
     ) {