Browse Source

Merge remote-tracking branch 'origin/pr/1793'

HapeLee 3 months ago
parent
commit
5db7b187ef

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

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

+ 14 - 8
app/src/main/java/io/legado/app/base/BaseActivity.kt

@@ -168,7 +168,7 @@ abstract class BaseActivity<VB : ViewBinding>(
     open fun initTheme() {
         when (getPrefString("app_theme", "0")) {
             "0" -> {
-                DynamicColors.applyToActivitiesIfAvailable(application)
+                DynamicColors.applyToActivityIfAvailable(this)
             }
             "1" -> setTheme(R.style.Theme_Base_GR)
             "2" -> setTheme(R.style.Theme_Base_Lemon)
@@ -182,10 +182,8 @@ abstract class BaseActivity<VB : ViewBinding>(
             "10" -> setTheme(R.style.Theme_Base_Phoebe)
             "11" -> setTheme(R.style.Theme_Base_Mujika)
             "12" -> {
-                if (AppConfig.customMode == "accent")
-                    setTheme(R.style.ThemeOverlay_WhiteBackground)
-
                 val colorImagePath = getPrefString(PreferKey.colorImage)
+                var colorImageApplied = false
                 if (!colorImagePath.isNullOrBlank()) {
                     val file = File(colorImagePath)
                     if (file.exists()) {
@@ -200,18 +198,26 @@ abstract class BaseActivity<VB : ViewBinding>(
                                 .setContentBasedSource(scaledBitmap)
                                 .build()
 
-                            DynamicColors.applyToActivitiesIfAvailable(application, options)
+                            DynamicColors.applyToActivityIfAvailable(this, options)
                             bitmap.recycle()
+                            colorImageApplied = true
                         }
                     }
-                }else{
-                    DynamicColors.applyToActivitiesIfAvailable(
-                        application,
+                }
+                if (!colorImageApplied) {
+                    // 取色图片缺失或解码失败时回退到种子色,
+                    // 否则该 Activity 完全拿不到动态配色,与 Compose 界面不一致
+                    DynamicColors.applyToActivityIfAvailable(
+                        this,
                         DynamicColorsOptions.Builder()
                             .setContentBasedSource(application.primaryColor)
                             .build()
                     )
                 }
+
+                // 必须在动态取色之后应用,否则会被动态配色的 surface/background 覆盖
+                if (AppConfig.customMode == "accent")
+                    setTheme(R.style.ThemeOverlay_WhiteBackground)
             }
 
             "13" -> setTheme(R.style.AppTheme_Transparent)

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

@@ -1,5 +1,6 @@
 package io.legado.app.base
 
+import android.content.res.Configuration
 import android.os.Bundle
 import android.os.Build
 import androidx.activity.compose.setContent
@@ -56,6 +57,18 @@ abstract class BaseComposeActivity(
         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() {
         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))
     }
 
+    /**
+     * 切换深浅色但不发 RECREATE:Compose 界面通过 ThemeConfig.themeMode 快照状态
+     * 自动换色(保留 ToggleButton 等组件的过渡动画),setDefaultNightMode 只会重建
+     * 未在 manifest 声明 uiMode configChanges 的旧 View Activity
+     */
+    fun applyDayNightLive() {
+        initNightMode()
+        postEvent(EventBus.UP_CONFIG, arrayListOf(2))
+    }
+
     fun applyDayNightInit(context: Context) {
         initNightMode()
     }

+ 1 - 22
app/src/main/java/io/legado/app/lib/prefs/ThemeCardPreference.kt

@@ -4,8 +4,6 @@ import android.annotation.SuppressLint
 import android.content.Context
 import android.content.res.ColorStateList
 import android.graphics.Color
-import android.os.Handler
-import android.os.Looper
 import android.util.AttributeSet
 import android.view.ContextThemeWrapper
 import android.view.LayoutInflater
@@ -20,11 +18,9 @@ import com.google.android.material.card.MaterialCardView
 import io.legado.app.R
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.PreferKey
-import io.legado.app.lib.dialogs.alert
 import io.legado.app.ui.config.themeConfig.ThemeConfig
 import io.legado.app.utils.getPrefString
 import io.legado.app.utils.postEvent
-import io.legado.app.utils.restart
 import io.legado.app.utils.toastOnUi
 import splitties.init.appCtx
 
@@ -98,29 +94,12 @@ class ThemeCardPreference(context: Context, attrs: AttributeSet) : Preference(co
                             ThemeConfig.containerOpacity = 0
                         }
                     }
-                    val oldValue = currentValue
                     currentValue = value
                     persistString(value)
                     callChangeListener(value)
                     ThemeConfig.appTheme = value
                     notifyDataSetChanged()
-                    val isDynamicSwitch = (oldValue == "12" || value == "12")
-                    Handler(Looper.getMainLooper()).postDelayed({
-                        if (isDynamicSwitch) {
-                            context.alert(context.getString(R.string.restart_required_message)) {
-                                okButton {
-                                    Handler(Looper.getMainLooper()).postDelayed({
-                                        context.restart()
-                                    }, 100)
-                                }
-                                cancelButton {
-                                    context.toastOnUi(R.string.restart_later_message)
-                                }
-                            }
-                        } else {
-                            postEvent(EventBus.RECREATE, "")
-                        }
-                    }, 100)
+                    postEvent(EventBus.RECREATE, "")
                 }
             }
         }

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

@@ -61,6 +61,10 @@ fun <T> prefDelegate(
         @Volatile
         private var currentValue: T = defaultValue
         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
 
         init {
@@ -131,9 +135,10 @@ fun <T> prefDelegate(
                     is Long -> appCtx.putPrefLong(key, value)
                     is Float -> appCtx.putPrefFloat(key, value)
                 }
-                // 同步写入 DataStore,确保持久化后再返回
-                runCatching {
-                    runBlocking {
+                // 异步串行写入 DataStore;runBlocking 会把整文件重写 + fsync
+                // 挂在调用线程(通常是主线程的点击回调)上,造成明显掉帧
+                scope.launch(dsWriteDispatcher) {
+                    runCatching {
                         when (value) {
                             is String? -> DsSync.putString(key, value)
                             is Int -> DsSync.putInt(key, value)

+ 3 - 9
app/src/main/java/io/legado/app/ui/config/customTheme/CustomThemeScreen.kt

@@ -23,11 +23,9 @@ import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringArrayResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import com.google.android.material.color.DynamicColors
-import com.google.android.material.color.DynamicColorsOptions
 import io.legado.app.R
+import io.legado.app.constant.EventBus
 import io.legado.app.lib.theme.ThemeStore
-import io.legado.app.lib.theme.primaryColor
 import io.legado.app.ui.config.themeConfig.ThemeConfig
 import io.legado.app.ui.theme.adaptiveContentPadding
 import io.legado.app.ui.widget.components.AppScaffold
@@ -39,6 +37,7 @@ import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
 import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
+import io.legado.app.utils.postEvent
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
@@ -245,12 +244,7 @@ fun CustomThemeScreen(
                     ThemeStore.editTheme(context)
                         .primaryColor(color)
                         .apply()
-                    DynamicColors.applyToActivitiesIfAvailable(
-                        context.applicationContext as android.app.Application,
-                        DynamicColorsOptions.Builder()
-                            .setContentBasedSource(context.primaryColor)
-                            .build()
-                    )
+                    postEvent(EventBus.RECREATE, "")
                 }
             }
         )

+ 31 - 52
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt

@@ -3,8 +3,6 @@ package io.legado.app.ui.config.themeConfig
 import android.annotation.SuppressLint
 import android.content.Context
 import android.content.Intent
-import android.os.Handler
-import android.os.Looper
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.animation.AnimatedVisibility
@@ -85,7 +83,6 @@ import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.FontFolderState
 import io.legado.app.ui.widget.components.FontSelectSheet
 import io.legado.app.ui.widget.components.SplicedColumnGroup
-import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.button.series.SmallPlainButton
 import io.legado.app.ui.widget.components.card.GlassCard
 import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
@@ -99,7 +96,6 @@ import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
 import io.legado.app.utils.postEvent
-import io.legado.app.utils.restart
 import io.legado.app.utils.takePersistablePermissionSafely
 import io.legado.app.utils.toastOnUi
 import org.koin.androidx.compose.koinViewModel
@@ -119,7 +115,6 @@ fun ThemeConfigScreen(
     var selectedThemeMode by remember { mutableStateOf(ThemeConfig.themeMode) }
     var selectedTheme by remember { mutableStateOf(ThemeConfig.appTheme) }
     var useMiuixMonet by remember { mutableStateOf(ThemeConfig.useMiuixMonet) }
-    var showRestartDialog by remember { mutableStateOf(false) }
     var showLauncherIconPicker by remember { mutableStateOf(false) }
     var showBorderColorPicker by remember { mutableStateOf(false) }
     var showNavIconSheet by remember { mutableStateOf(false) }
@@ -239,7 +234,7 @@ fun ThemeConfigScreen(
                             onValueChange = { mode ->
                                 selectedThemeMode = mode
                                 ThemeConfig.themeMode = mode
-                                ThemeConfigStore.applyDayNight(context)
+                                ThemeConfigStore.applyDayNightLive()
                             }
                         )
 
@@ -268,7 +263,7 @@ fun ThemeConfigScreen(
                                     selectedTheme = newTheme
                                     ThemeConfig.appTheme = newTheme
                                     if (oldTheme != newTheme) {
-                                        showRestartDialog = true
+                                        postEvent(EventBus.RECREATE, "")
                                     }
                                 }
                             )
@@ -279,7 +274,7 @@ fun ThemeConfigScreen(
                             onModeSelected = { mode ->
                                 selectedThemeMode = mode
                                 ThemeConfig.themeMode = mode
-                                ThemeConfigStore.applyDayNight(context)
+                                ThemeConfigStore.applyDayNightLive()
                             }
                         )
                     }
@@ -310,15 +305,9 @@ fun ThemeConfigScreen(
                                         ThemeConfig.containerOpacity = 0
                                     }
                                 }
-                                val oldTheme = selectedTheme
                                 selectedTheme = theme
                                 ThemeConfig.appTheme = theme
-                                val isDynamicSwitch = (oldTheme == "12" || theme == "12")
-                                if (isDynamicSwitch) {
-                                    showRestartDialog = true
-                                } else {
-                                    postEvent(EventBus.RECREATE, "")
-                                }
+                                postEvent(EventBus.RECREATE, "")
                             }
                         )
                     }
@@ -819,25 +808,6 @@ fun ThemeConfigScreen(
     }
 
 
-    AppAlertDialog(
-        show = showRestartDialog,
-        onDismissRequest = { showRestartDialog = false },
-        title = stringResource(R.string.restart_required_message),
-        onConfirm = {
-            showRestartDialog = false
-            Handler(Looper.getMainLooper()).postDelayed({
-                context.restart()
-            }, 100)
-        },
-        confirmText = stringResource(R.string.ok),
-        onDismiss = {
-            showRestartDialog = false
-            context.toastOnUi(R.string.restart_later_message)
-        },
-        dismissText = stringResource(R.string.cancel)
-    )
-
-
     BackgroundImageManageSheet(
         isDarkTheme = manageKey,
         onDismissRequest = { manageKey = null }
@@ -1005,15 +975,20 @@ fun ThemeColorButton(
     customNightSeedColor: Int,
     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(
         targetValue = if (isSelected) 2.dp else 0.dp,
         label = "borderWidth"
@@ -1106,15 +1081,19 @@ fun ThemeCard(
     customLightSeedColor: 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(
         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 主题模式数据
+    // themeMode 用 effectiveDarkTheme 归一化(System 已在上面解析成明确的深浅色),
+    // 这样"跟随系统"和"深色"在系统深色下产生相等的 LegadoThemeMode,
+    // staticCompositionLocalOf 不会触发全树重组
     val themeColors = remember(
         colorScheme, effectiveDarkTheme, themeSeedColor, paletteStyleValue, composeEngine,
-        appThemeMode, themeModeValue
+        appThemeMode
     ) {
         val paletteStyle = ThemeResolver.resolvePaletteStyle(paletteStyleValue)
-        val colorSchemeMode = ThemeResolver.resolveColorSchemeMode(themeModeValue)
+        val colorSchemeMode =
+            if (effectiveDarkTheme) ColorSchemeMode.Dark else ColorSchemeMode.Light
         LegadoThemeMode(
             colorScheme = colorScheme,
             isDark = effectiveDarkTheme,

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

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