Просмотр исходного кода

[优化] 优化了Compose的主题系统,现在可以处理背景图片、纯黑暗色模式等情况

HapeLee 10 месяцев назад
Родитель
Сommit
e25dae61c2

+ 6 - 5
app/src/main/java/io/legado/app/base/BaseActivity.kt

@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
 import android.content.Context
 import android.content.Context
 import android.content.res.Configuration
 import android.content.res.Configuration
 import android.graphics.BitmapFactory
 import android.graphics.BitmapFactory
-import android.graphics.Color
 import android.os.Build
 import android.os.Build
 import android.os.Bundle
 import android.os.Bundle
 import android.util.AttributeSet
 import android.util.AttributeSet
@@ -29,19 +28,20 @@ import io.legado.app.constant.PreferKey
 import io.legado.app.constant.Theme
 import io.legado.app.constant.Theme
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.ThemeConfig
 import io.legado.app.help.config.ThemeConfig
+import io.legado.app.lib.theme.primaryColor
+import io.legado.app.ui.theme.ThemeSyncer
 import io.legado.app.utils.applyOpenTint
 import io.legado.app.utils.applyOpenTint
 import io.legado.app.utils.applyTint
 import io.legado.app.utils.applyTint
 import io.legado.app.utils.disableAutoFill
 import io.legado.app.utils.disableAutoFill
+import io.legado.app.utils.fullScreen
 import io.legado.app.utils.getPrefString
 import io.legado.app.utils.getPrefString
 import io.legado.app.utils.hideSoftInput
 import io.legado.app.utils.hideSoftInput
 import io.legado.app.utils.observeEvent
 import io.legado.app.utils.observeEvent
-import io.legado.app.utils.toastOnUi
-import io.legado.app.utils.windowSize
-import io.legado.app.lib.theme.primaryColor
-import io.legado.app.utils.fullScreen
 import io.legado.app.utils.setNavigationBarColorAuto
 import io.legado.app.utils.setNavigationBarColorAuto
 import io.legado.app.utils.setStatusBarColorAuto
 import io.legado.app.utils.setStatusBarColorAuto
 import io.legado.app.utils.themeColor
 import io.legado.app.utils.themeColor
+import io.legado.app.utils.toastOnUi
+import io.legado.app.utils.windowSize
 import java.io.File
 import java.io.File
 
 
 
 
@@ -245,6 +245,7 @@ abstract class BaseActivity<VB : ViewBinding>(
 
 
     open fun observeLiveBus() {
     open fun observeLiveBus() {
         observeEvent<String>(EventBus.RECREATE) {
         observeEvent<String>(EventBus.RECREATE) {
+            ThemeSyncer.syncAll()
             recreate()
             recreate()
         }
         }
     }
     }

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

@@ -1,34 +1,23 @@
 package io.legado.app.base
 package io.legado.app.base
 
 
-import android.graphics.BitmapFactory
 import android.os.Bundle
 import android.os.Bundle
 import androidx.activity.compose.setContent
 import androidx.activity.compose.setContent
 import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Surface
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
 import androidx.core.graphics.drawable.toDrawable
 import androidx.core.graphics.drawable.toDrawable
-import androidx.core.graphics.scale
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowCompat
-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.constant.EventBus
-import io.legado.app.constant.PreferKey
 import io.legado.app.constant.Theme
 import io.legado.app.constant.Theme
-import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.ThemeConfig
 import io.legado.app.help.config.ThemeConfig
-import io.legado.app.lib.theme.primaryColor
 import io.legado.app.ui.theme.AppTheme
 import io.legado.app.ui.theme.AppTheme
-import io.legado.app.ui.theme.ThemeState
+import io.legado.app.ui.theme.ThemeSyncer
 import io.legado.app.utils.disableAutoFill
 import io.legado.app.utils.disableAutoFill
 import io.legado.app.utils.fullScreen
 import io.legado.app.utils.fullScreen
-import io.legado.app.utils.getPrefString
 import io.legado.app.utils.observeEvent
 import io.legado.app.utils.observeEvent
 import io.legado.app.utils.setStatusBarColorAuto
 import io.legado.app.utils.setStatusBarColorAuto
 import io.legado.app.utils.themeColor
 import io.legado.app.utils.themeColor
 import io.legado.app.utils.windowSize
 import io.legado.app.utils.windowSize
-import java.io.File
 
 
 abstract class BaseComposeActivity(
 abstract class BaseComposeActivity(
     val fullScreen: Boolean = true,
     val fullScreen: Boolean = true,
@@ -85,7 +74,7 @@ abstract class BaseComposeActivity(
 
 
     open fun observeLiveBus() {
     open fun observeLiveBus() {
         observeEvent<String>(EventBus.RECREATE) {
         observeEvent<String>(EventBus.RECREATE) {
-            ThemeState.updateThemeMode()
+            ThemeSyncer.syncAll()
             recreate()
             recreate()
         }
         }
     }
     }

+ 9 - 0
app/src/main/java/io/legado/app/help/config/AppConfig.kt

@@ -881,6 +881,15 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
             appCtx.getPrefBoolean(PreferKey.pureBlack, value)
             appCtx.getPrefBoolean(PreferKey.pureBlack, value)
         }
         }
 
 
+    val hasLightBg: Boolean
+        get() = !appCtx.getPrefString(PreferKey.bgImage).isNullOrEmpty()
+
+    val hasDarkBg: Boolean
+        get() = !appCtx.getPrefString(PreferKey.bgImageN).isNullOrEmpty()
+
+    val hasImageBg: Boolean
+        get() = hasLightBg && hasDarkBg
+
     var labelVisibilityMode
     var labelVisibilityMode
         get() = appCtx.getPrefString(PreferKey.labelVisibilityMode, "auto")
         get() = appCtx.getPrefString(PreferKey.labelVisibilityMode, "auto")
         set(value) {
         set(value) {

+ 2 - 0
app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt

@@ -32,6 +32,7 @@ import io.legado.app.lib.prefs.ThemeCardPreference
 import io.legado.app.lib.prefs.ThemeModePreference
 import io.legado.app.lib.prefs.ThemeModePreference
 import io.legado.app.lib.theme.ThemeStore
 import io.legado.app.lib.theme.ThemeStore
 import io.legado.app.lib.theme.primaryColor
 import io.legado.app.lib.theme.primaryColor
+import io.legado.app.ui.theme.ThemeSyncer
 import io.legado.app.ui.widget.number.NumberPickerDialog
 import io.legado.app.ui.widget.number.NumberPickerDialog
 import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
 import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
 import io.legado.app.utils.ColorUtils
 import io.legado.app.utils.ColorUtils
@@ -180,6 +181,7 @@ class ThemeConfigFragment : PreferenceFragmentCompat(),
             PreferKey.customMode -> handleRestartRequired()
             PreferKey.customMode -> handleRestartRequired()
 
 
             PreferKey.pureBlack -> {
             PreferKey.pureBlack -> {
+                ThemeSyncer.syncAll()
                 Handler(Looper.getMainLooper()).postDelayed({
                 Handler(Looper.getMainLooper()).postDelayed({
                     recreateActivities()
                     recreateActivities()
                 }, 100)
                 }, 100)

+ 9 - 57
app/src/main/java/io/legado/app/ui/theme/AppTheme.kt

@@ -1,33 +1,14 @@
 package io.legado.app.ui.theme
 package io.legado.app.ui.theme
 
 
-import android.os.Build
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.MaterialExpressiveTheme
 import androidx.compose.material3.MaterialExpressiveTheme
 import androidx.compose.material3.MotionScheme
 import androidx.compose.material3.MotionScheme
 import androidx.compose.material3.Shapes
 import androidx.compose.material3.Shapes
 import androidx.compose.material3.Typography
 import androidx.compose.material3.Typography
-import androidx.compose.material3.dynamicDarkColorScheme
-import androidx.compose.material3.dynamicLightColorScheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.getValue
-import androidx.compose.ui.platform.LocalContext
-import com.materialkolor.PaletteStyle
-import io.legado.app.help.config.AppConfig
-import io.legado.app.lib.theme.primaryColor
-import io.legado.app.ui.theme.colorScheme.AugustColorScheme
-import io.legado.app.ui.theme.colorScheme.CarlottaColorScheme
-import io.legado.app.ui.theme.colorScheme.ElinkColorScheme
-import io.legado.app.ui.theme.colorScheme.GRColorScheme
-import io.legado.app.ui.theme.colorScheme.KoharuColorScheme
-import io.legado.app.ui.theme.colorScheme.LemonColorScheme
-import io.legado.app.ui.theme.colorScheme.MujikaColorScheme
-import io.legado.app.ui.theme.colorScheme.PhoebeColorScheme
-import io.legado.app.ui.theme.colorScheme.SoraColorScheme
-import io.legado.app.ui.theme.colorScheme.TransparentColorScheme
-import io.legado.app.ui.theme.colorScheme.WHColorScheme
-import io.legado.app.ui.theme.colorScheme.YuukaColorScheme
 
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 @Composable
@@ -35,30 +16,16 @@ fun AppTheme(
     darkTheme: Boolean = isSystemInDarkTheme(),
     darkTheme: Boolean = isSystemInDarkTheme(),
     content: @Composable () -> Unit
     content: @Composable () -> Unit
 ) {
 ) {
-    val context = LocalContext.current
     val appThemeMode by ThemeState.themeMode.collectAsState()
     val appThemeMode by ThemeState.themeMode.collectAsState()
-    val isAmoled = AppConfig.pureBlack
+    val isPureBlack by ThemeState.isPureBlack.collectAsState()
+    val hasImageBg by ThemeState.hasImageBg.collectAsState()
 
 
-    val colorScheme = when (appThemeMode) {
-        AppThemeMode.Dynamic -> {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-                if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
-            } else {
-                GRColorScheme.getColorScheme(darkTheme, isAmoled)
-            }
-        }
-        AppThemeMode.CUSTOM -> {
-            CustomColorScheme(
-                context = context,
-                seed = context.primaryColor,
-                style = PaletteStyle.Vibrant,
-            ).getColorScheme(darkTheme, isAmoled)
-        }
-        else -> {
-            val scheme = colorSchemes.getOrDefault(appThemeMode, GRColorScheme)
-            scheme.getColorScheme(darkTheme, isAmoled)
-        }
-    }
+    val colorScheme = ThemeManager.getColorScheme(
+        mode = appThemeMode,
+        darkTheme = darkTheme,
+        isAmoled = isPureBlack,
+        isImageBg = hasImageBg
+    )
 
 
     MaterialExpressiveTheme(
     MaterialExpressiveTheme(
         colorScheme = colorScheme,
         colorScheme = colorScheme,
@@ -67,19 +34,4 @@ fun AppTheme(
         shapes = Shapes(),
         shapes = Shapes(),
         content = content
         content = content
     )
     )
-}
-
-private val colorSchemes: Map<AppThemeMode, BaseColorScheme> = mapOf(
-    AppThemeMode.GR to GRColorScheme,
-    AppThemeMode.Lemon to LemonColorScheme,
-    AppThemeMode.WH to WHColorScheme,
-    AppThemeMode.Elink to ElinkColorScheme,
-    AppThemeMode.Sora to SoraColorScheme,
-    AppThemeMode.August to AugustColorScheme,
-    AppThemeMode.Carlotta to CarlottaColorScheme,
-    AppThemeMode.Koharu to KoharuColorScheme,
-    AppThemeMode.Yuuka to YuukaColorScheme,
-    AppThemeMode.Phoebe to PhoebeColorScheme,
-    AppThemeMode.Mujika to MujikaColorScheme,
-    AppThemeMode.Transparent to TransparentColorScheme,
-)
+}

+ 2 - 13
app/src/main/java/io/legado/app/ui/theme/BaseColorScheme.kt

@@ -3,7 +3,6 @@ package io.legado.app.ui.theme
 import androidx.compose.material3.ColorScheme
 import androidx.compose.material3.ColorScheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.ui.graphics.Color
 
 
 abstract class BaseColorScheme {
 abstract class BaseColorScheme {
     abstract val lightScheme: ColorScheme
     abstract val lightScheme: ColorScheme
@@ -11,17 +10,7 @@ abstract class BaseColorScheme {
 
 
     @Composable
     @Composable
     @ReadOnlyComposable
     @ReadOnlyComposable
-    fun getColorScheme(
-        darkTheme: Boolean,
-        isAmoled: Boolean,
-    ): ColorScheme {
-        var scheme = if (darkTheme) darkScheme else lightScheme
-        if (darkTheme && isAmoled) {
-            scheme = scheme.copy(
-                background = Color.Black,
-                surface = Color.Black,
-            )
-        }
-        return scheme
+    fun getColorScheme(darkTheme: Boolean): ColorScheme {
+        return if (darkTheme) darkScheme else lightScheme
     }
     }
 }
 }

+ 17 - 26
app/src/main/java/io/legado/app/ui/theme/CustomColorScheme.kt

@@ -3,51 +3,42 @@ package io.legado.app.ui.theme
 import android.app.UiModeManager
 import android.app.UiModeManager
 import android.content.Context
 import android.content.Context
 import android.os.Build
 import android.os.Build
+import androidx.compose.material3.ColorScheme
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Color
 import androidx.core.content.getSystemService
 import androidx.core.content.getSystemService
 import com.materialkolor.Contrast
 import com.materialkolor.Contrast
 import com.materialkolor.PaletteStyle
 import com.materialkolor.PaletteStyle
 import com.materialkolor.dynamicColorScheme
 import com.materialkolor.dynamicColorScheme
 
 
-internal class CustomColorScheme(
+class CustomColorScheme(
     context: Context,
     context: Context,
     seed: Int,
     seed: Int,
     style: PaletteStyle,
     style: PaletteStyle,
 ) : BaseColorScheme() {
 ) : BaseColorScheme() {
-    private val custom = CustomCompatColorScheme(context, seed, style)
 
 
-    override val darkScheme
-        get() = custom.darkScheme
-
-    override val lightScheme
-        get() = custom.lightScheme
-}
+    private val contrastLevel: Double =
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            context.getSystemService<UiModeManager>()
+                ?.contrast
+                ?.toDouble()
+                ?: Contrast.Default.value
+        } else {
+            Contrast.Default.value
+        }
 
 
-private class CustomCompatColorScheme(
-    context: Context,
-    seed: Int,
-    style: PaletteStyle,
-) : BaseColorScheme() {
-    override val lightScheme = dynamicColorScheme(
+    override val lightScheme: ColorScheme = dynamicColorScheme(
         seedColor = Color(seed),
         seedColor = Color(seed),
         isDark = false,
         isDark = false,
         isAmoled = false,
         isAmoled = false,
         style = style,
         style = style,
-        contrastLevel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
-            context.getSystemService<UiModeManager>()?.contrast?.toDouble() ?: Contrast.Default.value
-        } else {
-            Contrast.Default.value
-        },
+        contrastLevel = contrastLevel
     )
     )
-    override val darkScheme = dynamicColorScheme(
+
+    override val darkScheme: ColorScheme = dynamicColorScheme(
         seedColor = Color(seed),
         seedColor = Color(seed),
         isDark = true,
         isDark = true,
         isAmoled = false,
         isAmoled = false,
         style = style,
         style = style,
-        contrastLevel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
-            context.getSystemService<UiModeManager>()?.contrast?.toDouble() ?: Contrast.Default.value
-        } else {
-            Contrast.Default.value
-        },
+        contrastLevel = contrastLevel
     )
     )
-}
+}

+ 97 - 0
app/src/main/java/io/legado/app/ui/theme/ThemeManager.kt

@@ -0,0 +1,97 @@
+package io.legado.app.ui.theme
+
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.ColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import com.materialkolor.PaletteStyle
+import io.legado.app.lib.theme.primaryColor
+import io.legado.app.ui.theme.colorScheme.AugustColorScheme
+import io.legado.app.ui.theme.colorScheme.CarlottaColorScheme
+import io.legado.app.ui.theme.colorScheme.ElinkColorScheme
+import io.legado.app.ui.theme.colorScheme.GRColorScheme
+import io.legado.app.ui.theme.colorScheme.KoharuColorScheme
+import io.legado.app.ui.theme.colorScheme.LemonColorScheme
+import io.legado.app.ui.theme.colorScheme.MujikaColorScheme
+import io.legado.app.ui.theme.colorScheme.PhoebeColorScheme
+import io.legado.app.ui.theme.colorScheme.SoraColorScheme
+import io.legado.app.ui.theme.colorScheme.TransparentColorScheme
+import io.legado.app.ui.theme.colorScheme.WHColorScheme
+import io.legado.app.ui.theme.colorScheme.YuukaColorScheme
+
+object ThemeManager {
+
+    val colorSchemes: Map<AppThemeMode, BaseColorScheme> = mapOf(
+        AppThemeMode.GR to GRColorScheme,
+        AppThemeMode.Lemon to LemonColorScheme,
+        AppThemeMode.WH to WHColorScheme,
+        AppThemeMode.Elink to ElinkColorScheme,
+        AppThemeMode.Sora to SoraColorScheme,
+        AppThemeMode.August to AugustColorScheme,
+        AppThemeMode.Carlotta to CarlottaColorScheme,
+        AppThemeMode.Koharu to KoharuColorScheme,
+        AppThemeMode.Yuuka to YuukaColorScheme,
+        AppThemeMode.Phoebe to PhoebeColorScheme,
+        AppThemeMode.Mujika to MujikaColorScheme,
+        AppThemeMode.Transparent to TransparentColorScheme,
+    )
+
+    @Composable
+    fun getColorScheme(
+        mode: AppThemeMode,
+        darkTheme: Boolean = isSystemInDarkTheme(),
+        isAmoled: Boolean,
+        isImageBg: Boolean,
+        forceOpaque: Boolean = false
+    ): ColorScheme {
+        val context = LocalContext.current
+
+        val actualMode = if (forceOpaque && mode == AppThemeMode.Transparent) {
+            AppThemeMode.WH
+        } else {
+            mode
+        }
+
+        var scheme = when (actualMode) {
+            AppThemeMode.Dynamic -> {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+                    if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(
+                        context
+                    )
+                } else GRColorScheme.getColorScheme(darkTheme)
+            }
+
+            AppThemeMode.CUSTOM -> {
+                CustomColorScheme(context, context.primaryColor, PaletteStyle.Vibrant)
+                    .getColorScheme(darkTheme)
+            }
+
+            else -> (colorSchemes[actualMode] ?: GRColorScheme).getColorScheme(darkTheme)
+        }
+
+        if (darkTheme && isAmoled) {
+            scheme = scheme.copy(
+                surface = Color.Black,
+                background = Color.Black,
+                surfaceContainerLow = Color(0xFF0A0A0A),
+                surfaceContainer = Color(0xFF121212)
+            )
+        }
+
+        return if (!forceOpaque && (isImageBg || actualMode == AppThemeMode.Transparent)) {
+            scheme.copy(
+                surface = Color.Transparent,
+                background = Color.Transparent,
+                surfaceContainerLow = Color.Transparent,
+                surfaceContainer = Color.Transparent
+            )
+        } else {
+            scheme
+        }
+    }
+
+}

+ 22 - 0
app/src/main/java/io/legado/app/ui/theme/ThemeResolver.kt

@@ -0,0 +1,22 @@
+package io.legado.app.ui.theme
+
+object ThemeResolver {
+
+    fun resolveThemeMode(value: String): AppThemeMode = when (value) {
+        "0" -> AppThemeMode.Dynamic
+        "1" -> AppThemeMode.GR
+        "2" -> AppThemeMode.Lemon
+        "3" -> AppThemeMode.WH
+        "4" -> AppThemeMode.Elink
+        "5" -> AppThemeMode.Sora
+        "6" -> AppThemeMode.August
+        "7" -> AppThemeMode.Carlotta
+        "8" -> AppThemeMode.Koharu
+        "9" -> AppThemeMode.Yuuka
+        "10" -> AppThemeMode.Phoebe
+        "11" -> AppThemeMode.Mujika
+        "12" -> AppThemeMode.CUSTOM
+        "13" -> AppThemeMode.Transparent
+        else -> AppThemeMode.Dynamic
+    }
+}

+ 26 - 21
app/src/main/java/io/legado/app/ui/theme/ThemeState.kt

@@ -1,37 +1,42 @@
 package io.legado.app.ui.theme
 package io.legado.app.ui.theme
 
 
+import io.legado.app.constant.PreferKey
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
+import io.legado.app.utils.getPrefBoolean
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import splitties.init.appCtx
 
 
 object ThemeState {
 object ThemeState {
 
 
-    private val _themeMode = MutableStateFlow(resolveThemeMode(AppConfig.AppTheme.toString()))
+    private val _themeMode =
+        MutableStateFlow(ThemeResolver.resolveThemeMode(AppConfig.AppTheme.toString()))
     val themeMode: StateFlow<AppThemeMode> = _themeMode.asStateFlow()
     val themeMode: StateFlow<AppThemeMode> = _themeMode.asStateFlow()
 
 
-    fun updateThemeMode() {
-        val newMode = resolveThemeMode(AppConfig.AppTheme.toString())
+    private val _isPureBlack =
+        MutableStateFlow(appCtx.getPrefBoolean(PreferKey.pureBlack, false))
+    val isPureBlack: StateFlow<Boolean> = _isPureBlack.asStateFlow()
+
+    private val _hasImageBg =
+        MutableStateFlow(AppConfig.hasImageBg)
+    val hasImageBg: StateFlow<Boolean> = _hasImageBg.asStateFlow()
+
+    fun updateThemeMode(newMode: AppThemeMode) {
         if (_themeMode.value != newMode) {
         if (_themeMode.value != newMode) {
             _themeMode.value = newMode
             _themeMode.value = newMode
         }
         }
     }
     }
-}
 
 
-fun resolveThemeMode(value: String): AppThemeMode = when (value) {
-    "0" -> AppThemeMode.Dynamic
-    "1" -> AppThemeMode.GR
-    "2" -> AppThemeMode.Lemon
-    "3" -> AppThemeMode.WH
-    "4" -> AppThemeMode.Elink
-    "5" -> AppThemeMode.Sora
-    "6" -> AppThemeMode.August
-    "7" -> AppThemeMode.Carlotta
-    "8" -> AppThemeMode.Koharu
-    "9" -> AppThemeMode.Yuuka
-    "10" -> AppThemeMode.Phoebe
-    "11" -> AppThemeMode.Mujika
-    "12" -> AppThemeMode.CUSTOM
-    "13" -> AppThemeMode.Transparent
-    else -> AppThemeMode.Dynamic
-}
+    fun updatePureBlack(enabled: Boolean) {
+        if (_isPureBlack.value != enabled) {
+            _isPureBlack.value = enabled
+        }
+    }
+
+    fun updateImageBg(enabled: Boolean) {
+        if (_hasImageBg.value != enabled) {
+            _hasImageBg.value = enabled
+        }
+    }
+}

+ 29 - 0
app/src/main/java/io/legado/app/ui/theme/ThemeSyncer.kt

@@ -0,0 +1,29 @@
+package io.legado.app.ui.theme
+
+import io.legado.app.constant.PreferKey
+import io.legado.app.help.config.AppConfig
+import io.legado.app.utils.getPrefBoolean
+import splitties.init.appCtx
+
+object ThemeSyncer {
+
+    fun syncAll() {
+        syncThemeMode()
+        syncPureBlack()
+        syncImageBg()
+    }
+
+    fun syncThemeMode() {
+        ThemeState.updateThemeMode(
+            ThemeResolver.resolveThemeMode(AppConfig.AppTheme.toString())
+        )
+    }
+
+    fun syncPureBlack() {
+        ThemeState.updatePureBlack(appCtx.getPrefBoolean(PreferKey.pureBlack, false))
+    }
+
+    fun syncImageBg() {
+        ThemeState.updateImageBg(AppConfig.hasImageBg)
+    }
+}

+ 2 - 3
app/src/main/java/io/legado/app/ui/widget/components/TextCard.kt

@@ -1,6 +1,5 @@
 package io.legado.app.ui.widget.components
 package io.legado.app.ui.widget.components
 
 
-import androidx.compose.ui.graphics.Color
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.padding
@@ -12,10 +11,10 @@ import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.Icon
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.MaterialTheme.colorScheme
 import androidx.compose.material3.MaterialTheme.colorScheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.font.FontWeight
@@ -73,7 +72,7 @@ fun TextCard(
                 Spacer(modifier = Modifier.width(spacing))
                 Spacer(modifier = Modifier.width(spacing))
             }
             }
 
 
-            Text(
+            AnimatedText(
                 text = text,
                 text = text,
                 style = textStyle,
                 style = textStyle,
                 color = contentColor,
                 color = contentColor,

+ 20 - 5
app/src/main/java/io/legado/app/ui/widget/components/exportComponents/FilePickerSheet.kt

@@ -1,20 +1,35 @@
 package io.legado.app.ui.widget.components.exportComponents
 package io.legado.app.ui.widget.components.exportComponents
 
 
 import android.webkit.MimeTypeMap
 import android.webkit.MimeTypeMap
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
 import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
 import androidx.compose.material.icons.filled.CloudUpload
 import androidx.compose.material.icons.filled.CloudUpload
 import androidx.compose.material.icons.filled.FolderOpen
 import androidx.compose.material.icons.filled.FolderOpen
 import androidx.compose.material.icons.filled.SaveAlt
 import androidx.compose.material.icons.filled.SaveAlt
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SheetState
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.R
+import io.legado.app.ui.widget.components.modalBottomSheet.GlobalModalBottomSheet
 
 
 enum class FilePickerSheetMode {
 enum class FilePickerSheetMode {
     DIR, FILE, EXPORT
     DIR, FILE, EXPORT
@@ -31,10 +46,10 @@ fun FilePickerSheet(
     onUpload: (() -> Unit)? = null,
     onUpload: (() -> Unit)? = null,
     allowExtensions: Array<String>? = null,
     allowExtensions: Array<String>? = null,
 ) {
 ) {
-    ModalBottomSheet(
+    GlobalModalBottomSheet(
         onDismissRequest = onDismissRequest,
         onDismissRequest = onDismissRequest,
         sheetState = sheetState,
         sheetState = sheetState,
-        containerColor = MaterialTheme.colorScheme.surface,
+        containerColor = { it.surface }
     ) {
     ) {
         Column(
         Column(
             modifier = Modifier
             modifier = Modifier

+ 66 - 0
app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/GlobalModalBottomSheet.kt

@@ -0,0 +1,66 @@
+package io.legado.app.ui.widget.components.modalBottomSheet
+
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.ColorScheme
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialExpressiveTheme
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.MotionScheme
+import androidx.compose.material3.Shapes
+import androidx.compose.material3.SheetState
+import androidx.compose.material3.Typography
+import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import io.legado.app.ui.theme.ThemeManager
+import io.legado.app.ui.theme.ThemeState
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun GlobalModalBottomSheet(
+    onDismissRequest: () -> Unit,
+    modifier: Modifier = Modifier,
+    sheetState: SheetState = rememberModalBottomSheetState(),
+    containerColor: (ColorScheme) -> Color = { it.surfaceContainerLow },
+    content: @Composable ColumnScope.() -> Unit
+) {
+    val themeMode by ThemeState.themeMode.collectAsState()
+    val isPureBlack by ThemeState.isPureBlack.collectAsState()
+    val hasImageBg by ThemeState.hasImageBg.collectAsState()
+
+    val colorScheme = ThemeManager.getColorScheme(
+        mode = themeMode,
+        isAmoled = isPureBlack,
+        isImageBg = hasImageBg,
+        forceOpaque = true
+    )
+
+    ModalBottomSheet(
+        onDismissRequest = onDismissRequest,
+        sheetState = sheetState,
+        containerColor = containerColor(colorScheme),
+        contentColor = colorScheme.onSurface
+    ) {
+        MaterialExpressiveTheme(
+            colorScheme = colorScheme,
+            typography = Typography(),
+            motionScheme = MotionScheme.expressive(),
+            shapes = Shapes()
+        ) {
+            Column(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .animateContentSize()
+                    .then(modifier),
+                content = content
+            )
+        }
+    }
+}