manager: Rewrite UI state management
This commit is contained in:
@@ -6,114 +6,187 @@ import androidx.compose.material3.CardDefaults
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.luminance
|
import androidx.compose.ui.graphics.luminance
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Stable
|
||||||
object CardConfig {
|
object CardConfig {
|
||||||
// 卡片透明度
|
// 卡片透明度
|
||||||
var cardAlpha by mutableFloatStateOf(1f)
|
var cardAlpha by mutableFloatStateOf(1f)
|
||||||
|
internal set
|
||||||
// 卡片亮度
|
// 卡片亮度
|
||||||
var cardDim by mutableFloatStateOf(0f)
|
var cardDim by mutableFloatStateOf(0f)
|
||||||
|
internal set
|
||||||
// 卡片阴影
|
// 卡片阴影
|
||||||
var cardElevation by mutableStateOf(0.dp)
|
var cardElevation by mutableStateOf(0.dp)
|
||||||
var isShadowEnabled by mutableStateOf(true)
|
internal set
|
||||||
var isCustomAlphaSet by mutableStateOf(false)
|
|
||||||
var isCustomDimSet by mutableStateOf(false)
|
// 功能开关
|
||||||
var isUserDarkModeEnabled by mutableStateOf(false)
|
var isShadowEnabled by mutableStateOf(true)
|
||||||
var isUserLightModeEnabled by mutableStateOf(false)
|
internal set
|
||||||
var isCustomBackgroundEnabled by mutableStateOf(false)
|
var isCustomBackgroundEnabled by mutableStateOf(false)
|
||||||
|
internal set
|
||||||
|
|
||||||
|
var isCustomAlphaSet by mutableStateOf(false)
|
||||||
|
internal set
|
||||||
|
var isCustomDimSet by mutableStateOf(false)
|
||||||
|
internal set
|
||||||
|
var isUserDarkModeEnabled by mutableStateOf(false)
|
||||||
|
internal set
|
||||||
|
var isUserLightModeEnabled by mutableStateOf(false)
|
||||||
|
internal set
|
||||||
|
|
||||||
|
// 配置键名
|
||||||
|
private object Keys {
|
||||||
|
const val CARD_ALPHA = "card_alpha"
|
||||||
|
const val CARD_DIM = "card_dim"
|
||||||
|
const val CUSTOM_BACKGROUND_ENABLED = "custom_background_enabled"
|
||||||
|
const val IS_SHADOW_ENABLED = "is_shadow_enabled"
|
||||||
|
const val IS_CUSTOM_ALPHA_SET = "is_custom_alpha_set"
|
||||||
|
const val IS_CUSTOM_DIM_SET = "is_custom_dim_set"
|
||||||
|
const val IS_USER_DARK_MODE_ENABLED = "is_user_dark_mode_enabled"
|
||||||
|
const val IS_USER_LIGHT_MODE_ENABLED = "is_user_light_mode_enabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAlpha(alpha: Float, isCustom: Boolean = true) {
|
||||||
|
cardAlpha = alpha.coerceIn(0f, 1f)
|
||||||
|
if (isCustom) isCustomAlphaSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateDim(dim: Float, isCustom: Boolean = true) {
|
||||||
|
cardDim = dim.coerceIn(0f, 1f)
|
||||||
|
if (isCustom) isCustomDimSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateShadow(enabled: Boolean, elevation: Dp = 4.dp) {
|
||||||
|
isShadowEnabled = enabled
|
||||||
|
cardElevation = if (enabled) elevation else 0.dp
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateBackground(enabled: Boolean) {
|
||||||
|
isCustomBackgroundEnabled = enabled
|
||||||
|
// 自定义背景时自动禁用阴影以获得更好的视觉效果
|
||||||
|
if (enabled) {
|
||||||
|
updateShadow(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateThemePreference(darkMode: Boolean?, lightMode: Boolean?) {
|
||||||
|
isUserDarkModeEnabled = darkMode ?: false
|
||||||
|
isUserLightModeEnabled = lightMode ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
cardAlpha = 1f
|
||||||
|
cardDim = 0f
|
||||||
|
cardElevation = 0.dp
|
||||||
|
isShadowEnabled = true
|
||||||
|
isCustomBackgroundEnabled = false
|
||||||
|
isCustomAlphaSet = false
|
||||||
|
isCustomDimSet = false
|
||||||
|
isUserDarkModeEnabled = false
|
||||||
|
isUserLightModeEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setThemeDefaults(isDarkMode: Boolean) {
|
||||||
|
if (!isCustomAlphaSet) {
|
||||||
|
updateAlpha(if (isDarkMode) 0.88f else 1f, false)
|
||||||
|
}
|
||||||
|
if (!isCustomDimSet) {
|
||||||
|
updateDim(if (isDarkMode) 0.25f else 0f, false)
|
||||||
|
}
|
||||||
|
// 暗色模式下默认启用轻微阴影
|
||||||
|
if (isDarkMode && !isCustomBackgroundEnabled) {
|
||||||
|
updateShadow(true, 2.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存卡片配置到SharedPreferences
|
|
||||||
*/
|
|
||||||
fun save(context: Context) {
|
fun save(context: Context) {
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val prefs = context.getSharedPreferences("card_settings", Context.MODE_PRIVATE)
|
||||||
prefs.edit().apply {
|
prefs.edit().apply {
|
||||||
putFloat("card_alpha", cardAlpha)
|
putFloat(Keys.CARD_ALPHA, cardAlpha)
|
||||||
putFloat("card_dim", cardDim)
|
putFloat(Keys.CARD_DIM, cardDim)
|
||||||
putBoolean("custom_background_enabled", isCustomBackgroundEnabled)
|
putBoolean(Keys.CUSTOM_BACKGROUND_ENABLED, isCustomBackgroundEnabled)
|
||||||
putBoolean("is_shadow_enabled", isShadowEnabled)
|
putBoolean(Keys.IS_SHADOW_ENABLED, isShadowEnabled)
|
||||||
putBoolean("is_custom_alpha_set", isCustomAlphaSet)
|
putBoolean(Keys.IS_CUSTOM_ALPHA_SET, isCustomAlphaSet)
|
||||||
putBoolean("is_custom_dim_set", isCustomDimSet)
|
putBoolean(Keys.IS_CUSTOM_DIM_SET, isCustomDimSet)
|
||||||
putBoolean("is_user_dark_mode_enabled", isUserDarkModeEnabled)
|
putBoolean(Keys.IS_USER_DARK_MODE_ENABLED, isUserDarkModeEnabled)
|
||||||
putBoolean("is_user_light_mode_enabled", isUserLightModeEnabled)
|
putBoolean(Keys.IS_USER_LIGHT_MODE_ENABLED, isUserLightModeEnabled)
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从SharedPreferences加载卡片配置
|
|
||||||
*/
|
|
||||||
fun load(context: Context) {
|
fun load(context: Context) {
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val prefs = context.getSharedPreferences("card_settings", Context.MODE_PRIVATE)
|
||||||
cardAlpha = prefs.getFloat("card_alpha", 1f)
|
cardAlpha = prefs.getFloat(Keys.CARD_ALPHA, 1f).coerceIn(0f, 1f)
|
||||||
cardDim = prefs.getFloat("card_dim", 0f)
|
cardDim = prefs.getFloat(Keys.CARD_DIM, 0f).coerceIn(0f, 1f)
|
||||||
isCustomBackgroundEnabled = prefs.getBoolean("custom_background_enabled", false)
|
isCustomBackgroundEnabled = prefs.getBoolean(Keys.CUSTOM_BACKGROUND_ENABLED, false)
|
||||||
isShadowEnabled = prefs.getBoolean("is_shadow_enabled", true)
|
isShadowEnabled = prefs.getBoolean(Keys.IS_SHADOW_ENABLED, true)
|
||||||
isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false)
|
isCustomAlphaSet = prefs.getBoolean(Keys.IS_CUSTOM_ALPHA_SET, false)
|
||||||
isCustomDimSet = prefs.getBoolean("is_custom_dim_set", false)
|
isCustomDimSet = prefs.getBoolean(Keys.IS_CUSTOM_DIM_SET, false)
|
||||||
isUserDarkModeEnabled = prefs.getBoolean("is_user_dark_mode_enabled", false)
|
isUserDarkModeEnabled = prefs.getBoolean(Keys.IS_USER_DARK_MODE_ENABLED, false)
|
||||||
isUserLightModeEnabled = prefs.getBoolean("is_user_light_mode_enabled", false)
|
isUserLightModeEnabled = prefs.getBoolean(Keys.IS_USER_LIGHT_MODE_ENABLED, false)
|
||||||
updateShadowEnabled(isShadowEnabled)
|
|
||||||
|
// 应用阴影设置
|
||||||
|
updateShadow(isShadowEnabled, if (isShadowEnabled) 4.dp else 0.dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Deprecated("使用 updateShadow 替代", ReplaceWith("updateShadow(enabled)"))
|
||||||
* 更新阴影启用状态
|
|
||||||
*/
|
|
||||||
fun updateShadowEnabled(enabled: Boolean) {
|
fun updateShadowEnabled(enabled: Boolean) {
|
||||||
isShadowEnabled = enabled
|
updateShadow(enabled)
|
||||||
cardElevation = 0.dp
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置主题模式默认值
|
|
||||||
*/
|
|
||||||
fun setThemeDefaults(isDarkMode: Boolean) {
|
|
||||||
if (!isCustomAlphaSet) {
|
|
||||||
cardAlpha = 1f
|
|
||||||
}
|
|
||||||
if (!isCustomDimSet) {
|
|
||||||
cardDim = if (isDarkMode) 0.5f else 0f
|
|
||||||
}
|
|
||||||
updateShadowEnabled(isShadowEnabled)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
object CardStyleProvider {
|
||||||
* 获取卡片颜色配置
|
|
||||||
*/
|
@Composable
|
||||||
@Composable
|
fun getCardColors(originalColor: Color) = CardDefaults.cardColors(
|
||||||
fun getCardColors(originalColor: Color) = CardDefaults.cardColors(
|
|
||||||
containerColor = originalColor.copy(alpha = CardConfig.cardAlpha),
|
containerColor = originalColor.copy(alpha = CardConfig.cardAlpha),
|
||||||
contentColor = determineContentColor(originalColor)
|
contentColor = determineContentColor(originalColor),
|
||||||
)
|
disabledContainerColor = originalColor.copy(alpha = CardConfig.cardAlpha * 0.38f),
|
||||||
|
disabledContentColor = determineContentColor(originalColor).copy(alpha = 0.38f)
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
@Composable
|
||||||
* 获取卡片阴影配置
|
fun getCardElevation() = CardDefaults.cardElevation(
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun getCardElevation() = CardDefaults.cardElevation(
|
|
||||||
defaultElevation = CardConfig.cardElevation,
|
defaultElevation = CardConfig.cardElevation,
|
||||||
pressedElevation = CardConfig.cardElevation,
|
pressedElevation = if (CardConfig.isShadowEnabled) {
|
||||||
focusedElevation = CardConfig.cardElevation,
|
(CardConfig.cardElevation.value + 4).dp
|
||||||
hoveredElevation = CardConfig.cardElevation,
|
} else 0.dp,
|
||||||
draggedElevation = CardConfig.cardElevation,
|
focusedElevation = if (CardConfig.isShadowEnabled) {
|
||||||
disabledElevation = CardConfig.cardElevation
|
(CardConfig.cardElevation.value + 6).dp
|
||||||
)
|
} else 0.dp,
|
||||||
|
hoveredElevation = if (CardConfig.isShadowEnabled) {
|
||||||
|
(CardConfig.cardElevation.value + 2).dp
|
||||||
|
} else 0.dp,
|
||||||
|
draggedElevation = if (CardConfig.isShadowEnabled) {
|
||||||
|
(CardConfig.cardElevation.value + 8).dp
|
||||||
|
} else 0.dp,
|
||||||
|
disabledElevation = 0.dp
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
@Composable
|
||||||
* 根据背景颜色、主题模式和用户设置确定内容颜色
|
private fun determineContentColor(originalColor: Color): Color {
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
private fun determineContentColor(originalColor: Color): Color {
|
|
||||||
val isDarkTheme = isSystemInDarkTheme()
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
if (ThemeConfig.isThemeChanging) {
|
|
||||||
return if (isDarkTheme) Color.White else Color.Black
|
|
||||||
}
|
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
|
ThemeConfig.isThemeChanging -> {
|
||||||
|
if (isDarkTheme) Color.White else Color.Black
|
||||||
|
}
|
||||||
CardConfig.isUserLightModeEnabled -> Color.Black
|
CardConfig.isUserLightModeEnabled -> Color.Black
|
||||||
!isDarkTheme && originalColor.luminance() > 0.5f -> Color.Black
|
CardConfig.isUserDarkModeEnabled -> Color.White
|
||||||
isDarkTheme -> Color.White
|
else -> {
|
||||||
else -> if (originalColor.luminance() > 0.5f) Color.Black else Color.White
|
val luminance = originalColor.luminance()
|
||||||
|
val threshold = if (isDarkTheme) 0.4f else 0.6f
|
||||||
|
if (luminance > threshold) Color.Black else Color.White
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 向后兼容
|
||||||
|
@Composable
|
||||||
|
fun getCardColors(originalColor: Color) = CardStyleProvider.getCardColors(originalColor)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getCardElevation() = CardStyleProvider.getCardElevation()
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.sukisu.ultra.ui.theme
|
package com.sukisu.ultra.ui.theme
|
||||||
|
|
||||||
import android.content.ContentResolver
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -9,9 +8,7 @@ import androidx.activity.ComponentActivity
|
|||||||
import androidx.activity.SystemBarStyle
|
import androidx.activity.SystemBarStyle
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.compose.animation.core.animateFloat
|
import androidx.compose.animation.core.*
|
||||||
import androidx.compose.animation.core.spring
|
|
||||||
import androidx.compose.animation.core.updateTransition
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -28,7 +25,6 @@ import androidx.compose.ui.graphics.graphicsLayer
|
|||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
@@ -36,28 +32,31 @@ import coil.compose.AsyncImagePainter
|
|||||||
import coil.compose.rememberAsyncImagePainter
|
import coil.compose.rememberAsyncImagePainter
|
||||||
import com.sukisu.ultra.ui.theme.util.BackgroundTransformation
|
import com.sukisu.ultra.ui.theme.util.BackgroundTransformation
|
||||||
import com.sukisu.ultra.ui.theme.util.saveTransformedBackground
|
import com.sukisu.ultra.ui.theme.util.saveTransformedBackground
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
/**
|
@Stable
|
||||||
* 主题配置对象,管理应用的主题相关状态
|
|
||||||
*/
|
|
||||||
object ThemeConfig {
|
object ThemeConfig {
|
||||||
|
// 主题状态
|
||||||
var customBackgroundUri by mutableStateOf<Uri?>(null)
|
var customBackgroundUri by mutableStateOf<Uri?>(null)
|
||||||
var forceDarkMode by mutableStateOf<Boolean?>(null)
|
var forceDarkMode by mutableStateOf<Boolean?>(null)
|
||||||
var currentTheme by mutableStateOf<ThemeColors>(ThemeColors.Default)
|
var currentTheme by mutableStateOf<ThemeColors>(ThemeColors.Default)
|
||||||
var useDynamicColor by mutableStateOf(false)
|
var useDynamicColor by mutableStateOf(false)
|
||||||
|
|
||||||
|
// 背景状态
|
||||||
var backgroundImageLoaded by mutableStateOf(false)
|
var backgroundImageLoaded by mutableStateOf(false)
|
||||||
var needsResetOnThemeChange by mutableStateOf(false)
|
|
||||||
var isThemeChanging by mutableStateOf(false)
|
var isThemeChanging by mutableStateOf(false)
|
||||||
var preventBackgroundRefresh by mutableStateOf(false)
|
var preventBackgroundRefresh by mutableStateOf(false)
|
||||||
|
|
||||||
|
// 主题变化检测
|
||||||
private var lastDarkModeState: Boolean? = null
|
private var lastDarkModeState: Boolean? = null
|
||||||
|
|
||||||
fun detectThemeChange(currentDarkMode: Boolean): Boolean {
|
fun detectThemeChange(currentDarkMode: Boolean): Boolean {
|
||||||
val isChanged = lastDarkModeState != null && lastDarkModeState != currentDarkMode
|
val hasChanged = lastDarkModeState != null && lastDarkModeState != currentDarkMode
|
||||||
lastDarkModeState = currentDarkMode
|
lastDarkModeState = currentDarkMode
|
||||||
return isChanged
|
return hasChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetBackgroundState() {
|
fun resetBackgroundState() {
|
||||||
@@ -66,11 +65,171 @@ object ThemeConfig {
|
|||||||
}
|
}
|
||||||
isThemeChanging = true
|
isThemeChanging = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateTheme(
|
||||||
|
theme: ThemeColors? = null,
|
||||||
|
dynamicColor: Boolean? = null,
|
||||||
|
darkMode: Boolean? = null
|
||||||
|
) {
|
||||||
|
theme?.let { currentTheme = it }
|
||||||
|
dynamicColor?.let { useDynamicColor = it }
|
||||||
|
darkMode?.let { forceDarkMode = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
customBackgroundUri = null
|
||||||
|
forceDarkMode = null
|
||||||
|
currentTheme = ThemeColors.Default
|
||||||
|
useDynamicColor = false
|
||||||
|
backgroundImageLoaded = false
|
||||||
|
isThemeChanging = false
|
||||||
|
preventBackgroundRefresh = false
|
||||||
|
lastDarkModeState = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ThemeManager {
|
||||||
|
private const val PREFS_NAME = "theme_prefs"
|
||||||
|
|
||||||
|
fun saveThemeMode(context: Context, forceDark: Boolean?) {
|
||||||
|
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
|
||||||
|
putString("theme_mode", when (forceDark) {
|
||||||
|
true -> "dark"
|
||||||
|
false -> "light"
|
||||||
|
null -> "system"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ThemeConfig.forceDarkMode = forceDark
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadThemeMode(context: Context) {
|
||||||
|
val mode = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
.getString("theme_mode", "system")
|
||||||
|
|
||||||
|
ThemeConfig.forceDarkMode = when (mode) {
|
||||||
|
"dark" -> true
|
||||||
|
"light" -> false
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveThemeColors(context: Context, themeName: String) {
|
||||||
|
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
|
||||||
|
putString("theme_colors", themeName)
|
||||||
|
}
|
||||||
|
ThemeConfig.currentTheme = ThemeColors.fromName(themeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadThemeColors(context: Context) {
|
||||||
|
val themeName = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
.getString("theme_colors", "default") ?: "default"
|
||||||
|
ThemeConfig.currentTheme = ThemeColors.fromName(themeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveDynamicColorState(context: Context, enabled: Boolean) {
|
||||||
|
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
|
||||||
|
putBoolean("use_dynamic_color", enabled)
|
||||||
|
}
|
||||||
|
ThemeConfig.useDynamicColor = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun loadDynamicColorState(context: Context) {
|
||||||
|
val enabled = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
.getBoolean("use_dynamic_color", Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||||
|
ThemeConfig.useDynamicColor = enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object BackgroundManager {
|
||||||
|
private const val TAG = "BackgroundManager"
|
||||||
|
|
||||||
|
fun saveAndApplyCustomBackground(
|
||||||
|
context: Context,
|
||||||
|
uri: Uri,
|
||||||
|
transformation: BackgroundTransformation? = null
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
val finalUri = if (transformation != null) {
|
||||||
|
context.saveTransformedBackground(uri, transformation)
|
||||||
|
} else {
|
||||||
|
copyImageToInternalStorage(context, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveBackgroundUri(context, finalUri)
|
||||||
|
ThemeConfig.customBackgroundUri = finalUri
|
||||||
|
CardConfig.updateBackground(true)
|
||||||
|
resetBackgroundState(context)
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "保存背景失败: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearCustomBackground(context: Context) {
|
||||||
|
saveBackgroundUri(context, null)
|
||||||
|
ThemeConfig.customBackgroundUri = null
|
||||||
|
CardConfig.updateBackground(false)
|
||||||
|
resetBackgroundState(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadCustomBackground(context: Context) {
|
||||||
|
val uriString = context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
|
.getString("custom_background", null)
|
||||||
|
|
||||||
|
val newUri = uriString?.toUri()
|
||||||
|
val preventRefresh = context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
|
.getBoolean("prevent_background_refresh", false)
|
||||||
|
|
||||||
|
ThemeConfig.preventBackgroundRefresh = preventRefresh
|
||||||
|
|
||||||
|
if (!preventRefresh || ThemeConfig.customBackgroundUri?.toString() != newUri?.toString()) {
|
||||||
|
Log.d(TAG, "加载自定义背景: $uriString")
|
||||||
|
ThemeConfig.customBackgroundUri = newUri
|
||||||
|
ThemeConfig.backgroundImageLoaded = false
|
||||||
|
CardConfig.updateBackground(newUri != null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveBackgroundUri(context: Context, uri: Uri?) {
|
||||||
|
context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE).edit {
|
||||||
|
putString("custom_background", uri?.toString())
|
||||||
|
putBoolean("prevent_background_refresh", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetBackgroundState(context: Context) {
|
||||||
|
ThemeConfig.backgroundImageLoaded = false
|
||||||
|
ThemeConfig.preventBackgroundRefresh = false
|
||||||
|
context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE).edit {
|
||||||
|
putBoolean("prevent_background_refresh", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyImageToInternalStorage(context: Context, uri: Uri): Uri? {
|
||||||
|
return try {
|
||||||
|
val inputStream = context.contentResolver.openInputStream(uri) ?: return null
|
||||||
|
val fileName = "custom_background_${System.currentTimeMillis()}.jpg"
|
||||||
|
val file = File(context.filesDir, fileName)
|
||||||
|
|
||||||
|
FileOutputStream(file).use { outputStream ->
|
||||||
|
val buffer = ByteArray(8 * 1024)
|
||||||
|
var read: Int
|
||||||
|
while (inputStream.read(buffer).also { read = it } != -1) {
|
||||||
|
outputStream.write(buffer, 0, read)
|
||||||
|
}
|
||||||
|
outputStream.flush()
|
||||||
|
}
|
||||||
|
inputStream.close()
|
||||||
|
|
||||||
|
Uri.fromFile(file)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "复制图片失败: ${e.message}", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用主题
|
|
||||||
*/
|
|
||||||
@Composable
|
@Composable
|
||||||
fun KernelSUTheme(
|
fun KernelSUTheme(
|
||||||
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
|
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
|
||||||
@@ -84,162 +243,161 @@ fun KernelSUTheme(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val systemIsDark = isSystemInDarkTheme()
|
val systemIsDark = isSystemInDarkTheme()
|
||||||
|
|
||||||
// 检测系统主题变化并保存状态
|
// 初始化主题
|
||||||
val themeChanged = ThemeConfig.detectThemeChange(systemIsDark)
|
ThemeInitializer(context = context, systemIsDark = systemIsDark)
|
||||||
LaunchedEffect(systemIsDark, themeChanged) {
|
|
||||||
if (ThemeConfig.forceDarkMode == null && themeChanged) {
|
|
||||||
Log.d("ThemeSystem", "系统主题变化检测: 从 ${!systemIsDark} 变为 $systemIsDark")
|
|
||||||
ThemeConfig.resetBackgroundState()
|
|
||||||
|
|
||||||
if (!ThemeConfig.preventBackgroundRefresh) {
|
|
||||||
context.loadCustomBackground()
|
|
||||||
}
|
|
||||||
|
|
||||||
CardConfig.apply {
|
|
||||||
load(context)
|
|
||||||
if (!isCustomAlphaSet) {
|
|
||||||
cardAlpha = if (systemIsDark) 0.50f else 1f
|
|
||||||
}
|
|
||||||
if (!isCustomDimSet) {
|
|
||||||
cardDim = if (systemIsDark) 0.5f else 0f
|
|
||||||
}
|
|
||||||
save(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemBarStyle(
|
|
||||||
darkMode = darkTheme
|
|
||||||
)
|
|
||||||
|
|
||||||
// 初始加载配置
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
context.loadThemeMode()
|
|
||||||
context.loadThemeColors()
|
|
||||||
context.loadDynamicColorState()
|
|
||||||
CardConfig.load(context)
|
|
||||||
|
|
||||||
if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) {
|
|
||||||
context.loadCustomBackground()
|
|
||||||
ThemeConfig.backgroundImageLoaded = false
|
|
||||||
}
|
|
||||||
|
|
||||||
ThemeConfig.preventBackgroundRefresh = context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
|
||||||
.getBoolean("prevent_background_refresh", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建颜色方案
|
// 创建颜色方案
|
||||||
val colorScheme = when {
|
val colorScheme = createColorScheme(context, darkTheme, dynamicColor)
|
||||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
|
||||||
if (darkTheme) createDynamicDarkColorScheme(context) else createDynamicLightColorScheme(context)
|
|
||||||
}
|
|
||||||
darkTheme -> createDarkColorScheme()
|
|
||||||
else -> createLightColorScheme()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据暗色模式和自定义背景调整卡片配置
|
// 系统栏样式
|
||||||
val isDarkModeWithCustomBackground = darkTheme && ThemeConfig.customBackgroundUri != null
|
SystemBarController(darkTheme)
|
||||||
if (darkTheme && !dynamicColor) {
|
|
||||||
CardConfig.setThemeDefaults(true)
|
|
||||||
} else if (!darkTheme && !dynamicColor) {
|
|
||||||
CardConfig.setThemeDefaults(false)
|
|
||||||
}
|
|
||||||
CardConfig.updateShadowEnabled(!isDarkModeWithCustomBackground)
|
|
||||||
|
|
||||||
val backgroundUri = rememberSaveable { mutableStateOf(ThemeConfig.customBackgroundUri) }
|
|
||||||
|
|
||||||
LaunchedEffect(ThemeConfig.customBackgroundUri) {
|
|
||||||
backgroundUri.value = ThemeConfig.customBackgroundUri
|
|
||||||
}
|
|
||||||
|
|
||||||
val bgImagePainter = backgroundUri.value?.let {
|
|
||||||
rememberAsyncImagePainter(
|
|
||||||
model = it,
|
|
||||||
onError = { err ->
|
|
||||||
Log.e("ThemeSystem", "背景图加载失败: ${err.result.throwable.message}")
|
|
||||||
ThemeConfig.customBackgroundUri = null
|
|
||||||
context.saveCustomBackground(null)
|
|
||||||
},
|
|
||||||
onSuccess = {
|
|
||||||
Log.d("ThemeSystem", "背景图加载成功")
|
|
||||||
ThemeConfig.backgroundImageLoaded = true
|
|
||||||
ThemeConfig.isThemeChanging = false
|
|
||||||
|
|
||||||
ThemeConfig.preventBackgroundRefresh = true
|
|
||||||
context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
|
||||||
.edit { putBoolean("prevent_background_refresh", true) }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val transition = updateTransition(
|
|
||||||
targetState = ThemeConfig.backgroundImageLoaded,
|
|
||||||
label = "bgTransition"
|
|
||||||
)
|
|
||||||
val bgAlpha by transition.animateFloat(
|
|
||||||
label = "bgAlpha",
|
|
||||||
transitionSpec = {
|
|
||||||
spring(
|
|
||||||
dampingRatio = 0.8f,
|
|
||||||
stiffness = 300f
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { loaded -> if (loaded) 1f else 0f }
|
|
||||||
|
|
||||||
DisposableEffect(systemIsDark) {
|
|
||||||
onDispose {
|
|
||||||
if (ThemeConfig.isThemeChanging) {
|
|
||||||
ThemeConfig.isThemeChanging = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算适用的暗化值
|
|
||||||
val dimFactor = CardConfig.cardDim
|
|
||||||
|
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = colorScheme,
|
colorScheme = colorScheme,
|
||||||
typography = Typography
|
typography = Typography
|
||||||
) {
|
) {
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
// 背景层
|
||||||
|
BackgroundLayer(darkTheme)
|
||||||
|
// 内容层
|
||||||
|
Box(modifier = Modifier.fillMaxSize().zIndex(1f)) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ThemeInitializer(context: Context, systemIsDark: Boolean) {
|
||||||
|
val themeChanged = ThemeConfig.detectThemeChange(systemIsDark)
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
// 处理系统主题变化
|
||||||
|
LaunchedEffect(systemIsDark, themeChanged) {
|
||||||
|
if (ThemeConfig.forceDarkMode == null && themeChanged) {
|
||||||
|
Log.d("ThemeSystem", "系统主题变化: $systemIsDark")
|
||||||
|
ThemeConfig.resetBackgroundState()
|
||||||
|
|
||||||
|
if (!ThemeConfig.preventBackgroundRefresh) {
|
||||||
|
BackgroundManager.loadCustomBackground(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
CardConfig.apply {
|
||||||
|
load(context)
|
||||||
|
setThemeDefaults(systemIsDark)
|
||||||
|
save(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始加载配置
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
scope.launch {
|
||||||
|
ThemeManager.loadThemeMode(context)
|
||||||
|
ThemeManager.loadThemeColors(context)
|
||||||
|
ThemeManager.loadDynamicColorState(context)
|
||||||
|
CardConfig.load(context)
|
||||||
|
|
||||||
|
if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) {
|
||||||
|
BackgroundManager.loadCustomBackground(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BackgroundLayer(darkTheme: Boolean) {
|
||||||
|
val backgroundUri = rememberSaveable { mutableStateOf(ThemeConfig.customBackgroundUri) }
|
||||||
|
|
||||||
|
LaunchedEffect(ThemeConfig.customBackgroundUri) {
|
||||||
|
backgroundUri.value = ThemeConfig.customBackgroundUri
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认背景
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.zIndex(-2f)
|
.zIndex(-2f)
|
||||||
.background(if (darkTheme) if (CardConfig.isCustomBackgroundEnabled) { colorScheme.surfaceContainerLow } else { colorScheme.background }
|
.background(
|
||||||
else if (CardConfig.isCustomBackgroundEnabled) { colorScheme.surfaceContainerLow } else { colorScheme.background })
|
if (CardConfig.isCustomBackgroundEnabled) {
|
||||||
|
MaterialTheme.colorScheme.surfaceContainerLow
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.background
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// 自定义背景层
|
// 自定义背景
|
||||||
backgroundUri.value?.let {
|
backgroundUri.value?.let { uri ->
|
||||||
|
CustomBackgroundLayer(uri = uri, darkTheme = darkTheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CustomBackgroundLayer(uri: Uri, darkTheme: Boolean) {
|
||||||
|
val painter = rememberAsyncImagePainter(
|
||||||
|
model = uri,
|
||||||
|
onError = { error ->
|
||||||
|
Log.e("ThemeSystem", "背景加载失败: ${error.result.throwable.message}")
|
||||||
|
ThemeConfig.customBackgroundUri = null
|
||||||
|
},
|
||||||
|
onSuccess = {
|
||||||
|
Log.d("ThemeSystem", "背景加载成功")
|
||||||
|
ThemeConfig.backgroundImageLoaded = true
|
||||||
|
ThemeConfig.isThemeChanging = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val transition = updateTransition(
|
||||||
|
targetState = ThemeConfig.backgroundImageLoaded,
|
||||||
|
label = "backgroundTransition"
|
||||||
|
)
|
||||||
|
|
||||||
|
val alpha by transition.animateFloat(
|
||||||
|
label = "backgroundAlpha",
|
||||||
|
transitionSpec = {
|
||||||
|
spring(
|
||||||
|
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||||
|
stiffness = Spring.StiffnessMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { loaded -> if (loaded) 1f else 0f }
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.zIndex(-1f)
|
.zIndex(-1f)
|
||||||
.alpha(bgAlpha)
|
.alpha(alpha)
|
||||||
) {
|
) {
|
||||||
// 背景图片
|
// 背景图片
|
||||||
bgImagePainter?.let { painter ->
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.paint(
|
.paint(painter = painter, contentScale = ContentScale.Crop)
|
||||||
painter = painter,
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
.graphicsLayer {
|
.graphicsLayer {
|
||||||
alpha = (painter.state as? AsyncImagePainter.State.Success)?.let { 1f } ?: 0f
|
this.alpha = (painter.state as? AsyncImagePainter.State.Success)?.let { 1f } ?: 0f
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// 亮度调节层 (根据cardDim调整)
|
// 遮罩层
|
||||||
|
BackgroundOverlay(darkTheme = darkTheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BackgroundOverlay(darkTheme: Boolean) {
|
||||||
|
val dimFactor = CardConfig.cardDim
|
||||||
|
|
||||||
|
// 主要遮罩层
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(
|
.background(
|
||||||
if (darkTheme) Color.Black.copy(alpha = 0.6f + dimFactor * 0.3f)
|
if (darkTheme) {
|
||||||
else Color.White.copy(alpha = 0.1f + dimFactor * 0.2f)
|
Color.Black.copy(alpha = 0.3f + dimFactor * 0.4f)
|
||||||
|
} else {
|
||||||
|
Color.White.copy(alpha = 0.05f + dimFactor * 0.3f)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -251,31 +409,57 @@ fun KernelSUTheme(
|
|||||||
Brush.radialGradient(
|
Brush.radialGradient(
|
||||||
colors = listOf(
|
colors = listOf(
|
||||||
Color.Transparent,
|
Color.Transparent,
|
||||||
if (darkTheme) Color.Black.copy(alpha = 0.5f + dimFactor * 0.2f)
|
if (darkTheme) {
|
||||||
else Color.Black.copy(alpha = 0.2f + dimFactor * 0.1f)
|
Color.Black.copy(alpha = 0.2f + dimFactor * 0.2f)
|
||||||
|
} else {
|
||||||
|
Color.Black.copy(alpha = 0.05f + dimFactor * 0.1f)
|
||||||
|
}
|
||||||
),
|
),
|
||||||
radius = 1200f
|
radius = 1000f
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 内容层
|
@Composable
|
||||||
Box(
|
private fun createColorScheme(
|
||||||
modifier = Modifier
|
context: Context,
|
||||||
.fillMaxSize()
|
darkTheme: Boolean,
|
||||||
.zIndex(1f)
|
dynamicColor: Boolean
|
||||||
) {
|
): ColorScheme {
|
||||||
content()
|
return when {
|
||||||
}
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
|
if (darkTheme) createDynamicDarkColorScheme(context)
|
||||||
|
else createDynamicLightColorScheme(context)
|
||||||
}
|
}
|
||||||
|
darkTheme -> createDarkColorScheme()
|
||||||
|
else -> createLightColorScheme()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SystemBarController(darkMode: Boolean) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val activity = context as ComponentActivity
|
||||||
|
|
||||||
|
SideEffect {
|
||||||
|
activity.enableEdgeToEdge(
|
||||||
|
statusBarStyle = SystemBarStyle.auto(
|
||||||
|
Color.Transparent.toArgb(),
|
||||||
|
Color.Transparent.toArgb(),
|
||||||
|
) { darkMode },
|
||||||
|
navigationBarStyle = if (darkMode) {
|
||||||
|
SystemBarStyle.dark(Color.Transparent.toArgb())
|
||||||
|
} else {
|
||||||
|
SystemBarStyle.light(
|
||||||
|
Color.Transparent.toArgb(),
|
||||||
|
Color.Transparent.toArgb()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建动态深色颜色方案
|
|
||||||
*/
|
|
||||||
@RequiresApi(Build.VERSION_CODES.S)
|
@RequiresApi(Build.VERSION_CODES.S)
|
||||||
@Composable
|
@Composable
|
||||||
private fun createDynamicDarkColorScheme(context: Context): ColorScheme {
|
private fun createDynamicDarkColorScheme(context: Context): ColorScheme {
|
||||||
@@ -288,9 +472,6 @@ private fun createDynamicDarkColorScheme(context: Context): ColorScheme {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建动态浅色颜色方案
|
|
||||||
*/
|
|
||||||
@RequiresApi(Build.VERSION_CODES.S)
|
@RequiresApi(Build.VERSION_CODES.S)
|
||||||
@Composable
|
@Composable
|
||||||
private fun createDynamicLightColorScheme(context: Context): ColorScheme {
|
private fun createDynamicLightColorScheme(context: Context): ColorScheme {
|
||||||
@@ -303,11 +484,6 @@ private fun createDynamicLightColorScheme(context: Context): ColorScheme {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建深色颜色方案
|
|
||||||
*/
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun createDarkColorScheme() = darkColorScheme(
|
private fun createDarkColorScheme() = darkColorScheme(
|
||||||
primary = ThemeConfig.currentTheme.primaryDark,
|
primary = ThemeConfig.currentTheme.primaryDark,
|
||||||
@@ -347,9 +523,6 @@ private fun createDarkColorScheme() = darkColorScheme(
|
|||||||
surfaceContainerHighest = ThemeConfig.currentTheme.surfaceContainerHighestDark,
|
surfaceContainerHighest = ThemeConfig.currentTheme.surfaceContainerHighestDark,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建浅色颜色方案
|
|
||||||
*/
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun createLightColorScheme() = lightColorScheme(
|
private fun createLightColorScheme() = lightColorScheme(
|
||||||
primary = ThemeConfig.currentTheme.primaryLight,
|
primary = ThemeConfig.currentTheme.primaryLight,
|
||||||
@@ -389,218 +562,32 @@ private fun createLightColorScheme() = lightColorScheme(
|
|||||||
surfaceContainerHighest = ThemeConfig.currentTheme.surfaceContainerHighestLight,
|
surfaceContainerHighest = ThemeConfig.currentTheme.surfaceContainerHighestLight,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 向后兼容
|
||||||
/**
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
* 复制图片到应用内部存储并提升持久性
|
|
||||||
*/
|
|
||||||
private fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
|
|
||||||
return try {
|
|
||||||
val contentResolver: ContentResolver = contentResolver
|
|
||||||
val inputStream: InputStream = contentResolver.openInputStream(uri) ?: return null
|
|
||||||
|
|
||||||
val fileName = "custom_background.jpg"
|
|
||||||
val file = File(filesDir, fileName)
|
|
||||||
|
|
||||||
val backupFile = File(filesDir, "${fileName}.backup")
|
|
||||||
val outputStream = FileOutputStream(backupFile)
|
|
||||||
val buffer = ByteArray(4 * 1024)
|
|
||||||
var read: Int
|
|
||||||
|
|
||||||
while (inputStream.read(buffer).also { read = it } != -1) {
|
|
||||||
outputStream.write(buffer, 0, read)
|
|
||||||
}
|
|
||||||
|
|
||||||
outputStream.flush()
|
|
||||||
outputStream.close()
|
|
||||||
inputStream.close()
|
|
||||||
|
|
||||||
if (file.exists()) {
|
|
||||||
file.delete()
|
|
||||||
}
|
|
||||||
backupFile.renameTo(file)
|
|
||||||
|
|
||||||
Uri.fromFile(file)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("ImageCopy", "复制图片失败: ${e.message}")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存并应用自定义背景
|
|
||||||
*/
|
|
||||||
fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) {
|
fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) {
|
||||||
val finalUri = if (transformation != null) {
|
kotlinx.coroutines.GlobalScope.launch {
|
||||||
saveTransformedBackground(uri, transformation)
|
BackgroundManager.saveAndApplyCustomBackground(this@saveAndApplyCustomBackground, uri, transformation)
|
||||||
} else {
|
|
||||||
copyImageToInternalStorage(uri)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存到配置文件
|
|
||||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
|
||||||
.edit {
|
|
||||||
putString("custom_background", finalUri?.toString())
|
|
||||||
// 设置阻止刷新标志为false,允许新设置的背景加载一次
|
|
||||||
putBoolean("prevent_background_refresh", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
ThemeConfig.customBackgroundUri = finalUri
|
|
||||||
ThemeConfig.backgroundImageLoaded = false
|
|
||||||
ThemeConfig.preventBackgroundRefresh = false
|
|
||||||
CardConfig.cardElevation = 0.dp
|
|
||||||
CardConfig.isCustomBackgroundEnabled = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存自定义背景
|
|
||||||
*/
|
|
||||||
fun Context.saveCustomBackground(uri: Uri?) {
|
fun Context.saveCustomBackground(uri: Uri?) {
|
||||||
val newUri = uri?.let { copyImageToInternalStorage(it) }
|
|
||||||
|
|
||||||
// 保存到配置文件
|
|
||||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
|
||||||
.edit {
|
|
||||||
putString("custom_background", newUri?.toString())
|
|
||||||
if (uri == null) {
|
|
||||||
// 如果清除背景,也重置阻止刷新标志
|
|
||||||
putBoolean("prevent_background_refresh", false)
|
|
||||||
} else {
|
|
||||||
// 设置阻止刷新标志为false,允许新设置的背景加载一次
|
|
||||||
putBoolean("prevent_background_refresh", false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ThemeConfig.customBackgroundUri = newUri
|
|
||||||
ThemeConfig.backgroundImageLoaded = false
|
|
||||||
ThemeConfig.preventBackgroundRefresh = false
|
|
||||||
|
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
CardConfig.cardElevation = 0.dp
|
saveAndApplyCustomBackground(uri)
|
||||||
CardConfig.isCustomBackgroundEnabled = true
|
} else {
|
||||||
|
BackgroundManager.clearCustomBackground(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载自定义背景
|
|
||||||
*/
|
|
||||||
fun Context.loadCustomBackground() {
|
|
||||||
val uriString = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
|
||||||
.getString("custom_background", null)
|
|
||||||
|
|
||||||
val newUri = uriString?.toUri()
|
|
||||||
val preventRefresh = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
|
||||||
.getBoolean("prevent_background_refresh", false)
|
|
||||||
|
|
||||||
ThemeConfig.preventBackgroundRefresh = preventRefresh
|
|
||||||
|
|
||||||
if (!preventRefresh || ThemeConfig.customBackgroundUri?.toString() != newUri?.toString()) {
|
|
||||||
Log.d("ThemeSystem", "加载自定义背景: $uriString, 阻止刷新: $preventRefresh")
|
|
||||||
ThemeConfig.customBackgroundUri = newUri
|
|
||||||
ThemeConfig.backgroundImageLoaded = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存主题模式
|
|
||||||
*/
|
|
||||||
fun Context.saveThemeMode(forceDark: Boolean?) {
|
fun Context.saveThemeMode(forceDark: Boolean?) {
|
||||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
ThemeManager.saveThemeMode(this, forceDark)
|
||||||
.edit {
|
|
||||||
putString(
|
|
||||||
"theme_mode", when (forceDark) {
|
|
||||||
true -> "dark"
|
|
||||||
false -> "light"
|
|
||||||
null -> "system"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ThemeConfig.forceDarkMode = forceDark
|
|
||||||
ThemeConfig.needsResetOnThemeChange = forceDark == null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载主题模式
|
|
||||||
*/
|
|
||||||
fun Context.loadThemeMode() {
|
|
||||||
val mode = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
|
||||||
.getString("theme_mode", "system")
|
|
||||||
|
|
||||||
ThemeConfig.forceDarkMode = when(mode) {
|
|
||||||
"dark" -> true
|
|
||||||
"light" -> false
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
ThemeConfig.needsResetOnThemeChange = ThemeConfig.forceDarkMode == null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存主题颜色
|
|
||||||
*/
|
|
||||||
fun Context.saveThemeColors(themeName: String) {
|
fun Context.saveThemeColors(themeName: String) {
|
||||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
ThemeManager.saveThemeColors(this, themeName)
|
||||||
.edit {
|
|
||||||
putString("theme_colors", themeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
ThemeConfig.currentTheme = ThemeColors.fromName(themeName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载主题颜色
|
|
||||||
*/
|
|
||||||
fun Context.loadThemeColors() {
|
|
||||||
val themeName = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
|
||||||
.getString("theme_colors", "default")
|
|
||||||
|
|
||||||
ThemeConfig.currentTheme = ThemeColors.fromName(themeName ?: "default")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存动态颜色状态
|
|
||||||
*/
|
|
||||||
fun Context.saveDynamicColorState(enabled: Boolean) {
|
fun Context.saveDynamicColorState(enabled: Boolean) {
|
||||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
ThemeManager.saveDynamicColorState(this, enabled)
|
||||||
.edit {
|
|
||||||
putBoolean("use_dynamic_color", enabled)
|
|
||||||
}
|
|
||||||
ThemeConfig.useDynamicColor = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载动态颜色状态
|
|
||||||
*/
|
|
||||||
fun Context.loadDynamicColorState() {
|
|
||||||
val enabled = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
|
||||||
.getBoolean("use_dynamic_color", true)
|
|
||||||
|
|
||||||
ThemeConfig.useDynamicColor = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SystemBarStyle(
|
|
||||||
darkMode: Boolean,
|
|
||||||
statusBarScrim: Color = Color.Transparent,
|
|
||||||
navigationBarScrim: Color = Color.Transparent,
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val activity = context as ComponentActivity
|
|
||||||
|
|
||||||
SideEffect {
|
|
||||||
activity.enableEdgeToEdge(
|
|
||||||
statusBarStyle = SystemBarStyle.auto(
|
|
||||||
statusBarScrim.toArgb(),
|
|
||||||
statusBarScrim.toArgb(),
|
|
||||||
) { darkMode },
|
|
||||||
navigationBarStyle = when {
|
|
||||||
darkMode -> SystemBarStyle.dark(
|
|
||||||
navigationBarScrim.toArgb()
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> SystemBarStyle.light(
|
|
||||||
navigationBarScrim.toArgb(),
|
|
||||||
navigationBarScrim.toArgb(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,23 @@
|
|||||||
package com.sukisu.ultra.ui.theme.component
|
package com.sukisu.ultra.ui.theme.component
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.*
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.gestures.detectTransformGestures
|
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.Fullscreen
|
import androidx.compose.material.icons.filled.Fullscreen
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
@@ -44,48 +43,62 @@ fun ImageEditorDialog(
|
|||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onConfirm: (Uri) -> Unit
|
onConfirm: (Uri) -> Unit
|
||||||
) {
|
) {
|
||||||
var scale by remember { mutableFloatStateOf(1f) }
|
// 图像变换状态
|
||||||
var offsetX by remember { mutableFloatStateOf(0f) }
|
val transformState = remember { ImageTransformState() }
|
||||||
var offsetY by remember { mutableFloatStateOf(0f) }
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var lastScale by remember { mutableFloatStateOf(1f) }
|
|
||||||
var lastOffsetX by remember { mutableFloatStateOf(0f) }
|
// 尺寸状态
|
||||||
var lastOffsetY by remember { mutableFloatStateOf(0f) }
|
|
||||||
var imageSize by remember { mutableStateOf(Size.Zero) }
|
var imageSize by remember { mutableStateOf(Size.Zero) }
|
||||||
var screenSize by remember { mutableStateOf(Size.Zero) }
|
var screenSize by remember { mutableStateOf(Size.Zero) }
|
||||||
|
|
||||||
|
// 动画状态
|
||||||
|
val animationSpec = spring<Float>(
|
||||||
|
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||||
|
stiffness = Spring.StiffnessMedium
|
||||||
|
)
|
||||||
|
|
||||||
val animatedScale by animateFloatAsState(
|
val animatedScale by animateFloatAsState(
|
||||||
targetValue = scale,
|
targetValue = transformState.scale,
|
||||||
|
animationSpec = animationSpec,
|
||||||
label = "ScaleAnimation"
|
label = "ScaleAnimation"
|
||||||
)
|
)
|
||||||
|
|
||||||
val animatedOffsetX by animateFloatAsState(
|
val animatedOffsetX by animateFloatAsState(
|
||||||
targetValue = offsetX,
|
targetValue = transformState.offsetX,
|
||||||
|
animationSpec = animationSpec,
|
||||||
label = "OffsetXAnimation"
|
label = "OffsetXAnimation"
|
||||||
)
|
)
|
||||||
|
|
||||||
val animatedOffsetY by animateFloatAsState(
|
val animatedOffsetY by animateFloatAsState(
|
||||||
targetValue = offsetY,
|
targetValue = transformState.offsetY,
|
||||||
|
animationSpec = animationSpec,
|
||||||
label = "OffsetYAnimation"
|
label = "OffsetYAnimation"
|
||||||
)
|
)
|
||||||
val updateTransformation = remember {
|
|
||||||
{ newScale: Float, newOffsetX: Float, newOffsetY: Float ->
|
// 工具函数
|
||||||
val scaleDiff = abs(newScale - lastScale)
|
|
||||||
val offsetXDiff = abs(newOffsetX - lastOffsetX)
|
|
||||||
val offsetYDiff = abs(newOffsetY - lastOffsetY)
|
|
||||||
if (scaleDiff > 0.01f || offsetXDiff > 1f || offsetYDiff > 1f) {
|
|
||||||
scale = newScale
|
|
||||||
offsetX = newOffsetX
|
|
||||||
offsetY = newOffsetY
|
|
||||||
lastScale = newScale
|
|
||||||
lastOffsetX = newOffsetX
|
|
||||||
lastOffsetY = newOffsetY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val scaleToFullScreen = remember {
|
val scaleToFullScreen = remember {
|
||||||
{
|
{
|
||||||
if (imageSize.height > 0 && screenSize.height > 0) {
|
if (imageSize.height > 0 && screenSize.height > 0) {
|
||||||
val newScale = screenSize.height / imageSize.height
|
val newScale = screenSize.height / imageSize.height
|
||||||
updateTransformation(newScale, 0f, 0f)
|
transformState.updateTransform(newScale, 0f, 0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val saveImage: () -> Unit = remember {
|
||||||
|
{
|
||||||
|
scope.launch {
|
||||||
|
try {
|
||||||
|
val transformation = BackgroundTransformation(
|
||||||
|
transformState.scale,
|
||||||
|
transformState.offsetX,
|
||||||
|
transformState.offsetY
|
||||||
|
)
|
||||||
|
val savedUri = context.saveTransformedBackground(imageUri, transformation)
|
||||||
|
savedUri?.let { onConfirm(it) }
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,11 +114,95 @@ fun ImageEditorDialog(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(Color.Black.copy(alpha = 0.9f))
|
.background(
|
||||||
|
Brush.radialGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.Black.copy(alpha = 0.9f),
|
||||||
|
Color.Black.copy(alpha = 0.95f)
|
||||||
|
),
|
||||||
|
radius = 800f
|
||||||
|
)
|
||||||
|
)
|
||||||
.onSizeChanged { size ->
|
.onSizeChanged { size ->
|
||||||
screenSize = Size(size.width.toFloat(), size.height.toFloat())
|
screenSize = Size(size.width.toFloat(), size.height.toFloat())
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
// 图像显示区域
|
||||||
|
ImageDisplayArea(
|
||||||
|
imageUri = imageUri,
|
||||||
|
animatedScale = animatedScale,
|
||||||
|
animatedOffsetX = animatedOffsetX,
|
||||||
|
animatedOffsetY = animatedOffsetY,
|
||||||
|
transformState = transformState,
|
||||||
|
onImageSizeChanged = { imageSize = it },
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
|
||||||
|
// 顶部工具栏
|
||||||
|
TopToolbar(
|
||||||
|
onDismiss = onDismiss,
|
||||||
|
onFullscreen = scaleToFullScreen,
|
||||||
|
onConfirm = saveImage,
|
||||||
|
modifier = Modifier.align(Alignment.TopCenter)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 底部提示信息
|
||||||
|
BottomHintCard(
|
||||||
|
modifier = Modifier.align(Alignment.BottomCenter)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图像变换状态管理类
|
||||||
|
*/
|
||||||
|
private class ImageTransformState {
|
||||||
|
var scale by mutableFloatStateOf(1f)
|
||||||
|
var offsetX by mutableFloatStateOf(0f)
|
||||||
|
var offsetY by mutableFloatStateOf(0f)
|
||||||
|
|
||||||
|
private var lastScale = 1f
|
||||||
|
private var lastOffsetX = 0f
|
||||||
|
private var lastOffsetY = 0f
|
||||||
|
|
||||||
|
fun updateTransform(newScale: Float, newOffsetX: Float, newOffsetY: Float) {
|
||||||
|
val scaleDiff = abs(newScale - lastScale)
|
||||||
|
val offsetXDiff = abs(newOffsetX - lastOffsetX)
|
||||||
|
val offsetYDiff = abs(newOffsetY - lastOffsetY)
|
||||||
|
|
||||||
|
if (scaleDiff > 0.01f || offsetXDiff > 1f || offsetYDiff > 1f) {
|
||||||
|
scale = newScale
|
||||||
|
offsetX = newOffsetX
|
||||||
|
offsetY = newOffsetY
|
||||||
|
lastScale = newScale
|
||||||
|
lastOffsetX = newOffsetX
|
||||||
|
lastOffsetY = newOffsetY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetToLast() {
|
||||||
|
scale = lastScale
|
||||||
|
offsetX = lastOffsetX
|
||||||
|
offsetY = lastOffsetY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图像显示区域组件
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun ImageDisplayArea(
|
||||||
|
imageUri: Uri,
|
||||||
|
animatedScale: Float,
|
||||||
|
animatedOffsetX: Float,
|
||||||
|
animatedOffsetY: Float,
|
||||||
|
transformState: ImageTransformState,
|
||||||
|
onImageSizeChanged: (Size) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
.data(imageUri)
|
.data(imageUri)
|
||||||
@@ -113,8 +210,7 @@ fun ImageEditorDialog(
|
|||||||
.build(),
|
.build(),
|
||||||
contentDescription = stringResource(R.string.settings_custom_background),
|
contentDescription = stringResource(R.string.settings_custom_background),
|
||||||
contentScale = ContentScale.Fit,
|
contentScale = ContentScale.Fit,
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
|
||||||
.graphicsLayer(
|
.graphicsLayer(
|
||||||
scaleX = animatedScale,
|
scaleX = animatedScale,
|
||||||
scaleY = animatedScale,
|
scaleY = animatedScale,
|
||||||
@@ -125,100 +221,191 @@ fun ImageEditorDialog(
|
|||||||
detectTransformGestures { _, pan, zoom, _ ->
|
detectTransformGestures { _, pan, zoom, _ ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
val newScale = (scale * zoom).coerceIn(0.5f, 3f)
|
val newScale = (transformState.scale * zoom).coerceIn(0.5f, 3f)
|
||||||
val maxOffsetX = max(0f, size.width * (newScale - 1) / 2)
|
val maxOffsetX = max(0f, size.width * (newScale - 1) / 2)
|
||||||
val maxOffsetY = max(0f, size.height * (newScale - 1) / 2)
|
val maxOffsetY = max(0f, size.height * (newScale - 1) / 2)
|
||||||
|
|
||||||
val newOffsetX = if (maxOffsetX > 0) {
|
val newOffsetX = if (maxOffsetX > 0) {
|
||||||
(offsetX + pan.x).coerceIn(-maxOffsetX, maxOffsetX)
|
(transformState.offsetX + pan.x).coerceIn(-maxOffsetX, maxOffsetX)
|
||||||
} else {
|
} else 0f
|
||||||
0f
|
|
||||||
}
|
|
||||||
val newOffsetY = if (maxOffsetY > 0) {
|
val newOffsetY = if (maxOffsetY > 0) {
|
||||||
(offsetY + pan.y).coerceIn(-maxOffsetY, maxOffsetY)
|
(transformState.offsetY + pan.y).coerceIn(-maxOffsetY, maxOffsetY)
|
||||||
} else {
|
} else 0f
|
||||||
0f
|
|
||||||
}
|
transformState.updateTransform(newScale, newOffsetX, newOffsetY)
|
||||||
updateTransformation(newScale, newOffsetX, newOffsetY)
|
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
updateTransformation(lastScale, lastOffsetX, lastOffsetY)
|
transformState.resetToLast()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onSizeChanged { size ->
|
.onSizeChanged { size ->
|
||||||
imageSize = Size(size.width.toFloat(), size.height.toFloat())
|
onImageSizeChanged(Size(size.width.toFloat(), size.height.toFloat()))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 顶部工具栏组件
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun TopToolbar(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onFullscreen: () -> Unit,
|
||||||
|
onConfirm: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp)
|
.padding(24.dp),
|
||||||
.align(Alignment.TopCenter),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
IconButton(
|
// 关闭按钮
|
||||||
|
ActionButton(
|
||||||
onClick = onDismiss,
|
onClick = onDismiss,
|
||||||
modifier = Modifier
|
icon = Icons.Default.Close,
|
||||||
.clip(RoundedCornerShape(8.dp))
|
|
||||||
.background(Color.Black.copy(alpha = 0.6f))
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Close,
|
|
||||||
contentDescription = stringResource(R.string.cancel),
|
contentDescription = stringResource(R.string.cancel),
|
||||||
tint = Color.White
|
backgroundColor = MaterialTheme.colorScheme.error.copy(alpha = 0.9f)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
IconButton(
|
// 全屏按钮
|
||||||
onClick = { scaleToFullScreen() },
|
ActionButton(
|
||||||
modifier = Modifier
|
onClick = onFullscreen,
|
||||||
.clip(RoundedCornerShape(8.dp))
|
icon = Icons.Default.Fullscreen,
|
||||||
.background(Color.Black.copy(alpha = 0.6f))
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Fullscreen,
|
|
||||||
contentDescription = stringResource(R.string.reprovision),
|
contentDescription = stringResource(R.string.reprovision),
|
||||||
tint = Color.White
|
backgroundColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.9f)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 确认按钮
|
||||||
|
ActionButton(
|
||||||
|
onClick = onConfirm,
|
||||||
|
icon = Icons.Default.Check,
|
||||||
|
contentDescription = stringResource(R.string.confirm),
|
||||||
|
backgroundColor = Color(0xFF4CAF50).copy(alpha = 0.9f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作按钮组件
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun ActionButton(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||||
|
contentDescription: String,
|
||||||
|
backgroundColor: Color,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
var isPressed by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val buttonScale by animateFloatAsState(
|
||||||
|
targetValue = if (isPressed) 0.85f else 1f,
|
||||||
|
animationSpec = spring(
|
||||||
|
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||||
|
stiffness = Spring.StiffnessHigh
|
||||||
|
),
|
||||||
|
label = "ButtonScale"
|
||||||
|
)
|
||||||
|
|
||||||
|
val buttonAlpha by animateFloatAsState(
|
||||||
|
targetValue = if (isPressed) 0.8f else 1f,
|
||||||
|
animationSpec = tween(100),
|
||||||
|
label = "ButtonAlpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
Surface(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
isPressed = true
|
||||||
try {
|
onClick()
|
||||||
val transformation = BackgroundTransformation(scale, offsetX, offsetY)
|
|
||||||
val savedUri = context.saveTransformedBackground(imageUri, transformation)
|
|
||||||
savedUri?.let { onConfirm(it) }
|
|
||||||
} catch (_: Exception) {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.size(64.dp)
|
||||||
.background(Color.Black.copy(alpha = 0.6f))
|
.graphicsLayer(
|
||||||
|
scaleX = buttonScale,
|
||||||
|
scaleY = buttonScale,
|
||||||
|
alpha = buttonAlpha
|
||||||
|
),
|
||||||
|
shape = CircleShape,
|
||||||
|
color = backgroundColor,
|
||||||
|
shadowElevation = 8.dp
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Check,
|
imageVector = icon,
|
||||||
contentDescription = stringResource(R.string.confirm),
|
contentDescription = contentDescription,
|
||||||
tint = Color.White
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(28.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
LaunchedEffect(isPressed) {
|
||||||
modifier = Modifier
|
if (isPressed) {
|
||||||
|
kotlinx.coroutines.delay(150)
|
||||||
|
isPressed = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 底部提示卡片组件
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun BottomHintCard(
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
var isVisible by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
val cardAlpha by animateFloatAsState(
|
||||||
|
targetValue = if (isVisible) 1f else 0f,
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = 500,
|
||||||
|
easing = EaseInOutCubic
|
||||||
|
),
|
||||||
|
label = "HintAlpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
val cardTranslationY by animateFloatAsState(
|
||||||
|
targetValue = if (isVisible) 0f else 100f,
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = 500,
|
||||||
|
easing = EaseInOutCubic
|
||||||
|
),
|
||||||
|
label = "HintTranslation"
|
||||||
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
kotlinx.coroutines.delay(4000)
|
||||||
|
isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp)
|
.padding(24.dp)
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.alpha(cardAlpha)
|
||||||
.background(Color.Black.copy(alpha = 0.6f))
|
.graphicsLayer {
|
||||||
.padding(16.dp)
|
translationY = cardTranslationY
|
||||||
.align(Alignment.BottomCenter)
|
},
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = Color.Black.copy(alpha = 0.85f)
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 12.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.image_editor_hint),
|
text = stringResource(id = R.string.image_editor_hint),
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(20.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -105,26 +105,24 @@ class MoreSettingsHandlers(
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
context.saveThemeMode(newThemeMode)
|
context.saveThemeMode(newThemeMode)
|
||||||
|
ThemeConfig.updateTheme(darkMode = newThemeMode)
|
||||||
|
|
||||||
when (index) {
|
when (index) {
|
||||||
2 -> { // 深色
|
2 -> { // 深色
|
||||||
ThemeConfig.forceDarkMode = true
|
ThemeConfig.updateTheme(darkMode = true)
|
||||||
CardConfig.isUserDarkModeEnabled = true
|
CardConfig.updateThemePreference(darkMode = true, lightMode = false)
|
||||||
CardConfig.isUserLightModeEnabled = false
|
|
||||||
CardConfig.setThemeDefaults(true)
|
CardConfig.setThemeDefaults(true)
|
||||||
CardConfig.save(context)
|
CardConfig.save(context)
|
||||||
}
|
}
|
||||||
1 -> { // 浅色
|
1 -> { // 浅色
|
||||||
ThemeConfig.forceDarkMode = false
|
ThemeConfig.updateTheme(darkMode = false)
|
||||||
CardConfig.isUserLightModeEnabled = true
|
CardConfig.updateThemePreference(darkMode = false, lightMode = true)
|
||||||
CardConfig.isUserDarkModeEnabled = false
|
|
||||||
CardConfig.setThemeDefaults(false)
|
CardConfig.setThemeDefaults(false)
|
||||||
CardConfig.save(context)
|
CardConfig.save(context)
|
||||||
}
|
}
|
||||||
0 -> { // 跟随系统
|
0 -> { // 跟随系统
|
||||||
ThemeConfig.forceDarkMode = null
|
ThemeConfig.updateTheme(darkMode = null)
|
||||||
CardConfig.isUserLightModeEnabled = false
|
CardConfig.updateThemePreference(darkMode = null, lightMode = null)
|
||||||
CardConfig.isUserDarkModeEnabled = false
|
|
||||||
val isNightModeActive = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
val isNightModeActive = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||||
CardConfig.setThemeDefaults(isNightModeActive)
|
CardConfig.setThemeDefaults(isNightModeActive)
|
||||||
CardConfig.save(context)
|
CardConfig.save(context)
|
||||||
@@ -145,6 +143,7 @@ class MoreSettingsHandlers(
|
|||||||
ThemeColors.Yellow -> "yellow"
|
ThemeColors.Yellow -> "yellow"
|
||||||
else -> "default"
|
else -> "default"
|
||||||
})
|
})
|
||||||
|
ThemeConfig.updateTheme(theme = theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,6 +152,7 @@ class MoreSettingsHandlers(
|
|||||||
fun handleDynamicColorChange(enabled: Boolean) {
|
fun handleDynamicColorChange(enabled: Boolean) {
|
||||||
state.useDynamicColor = enabled
|
state.useDynamicColor = enabled
|
||||||
context.saveDynamicColorState(enabled)
|
context.saveDynamicColorState(enabled)
|
||||||
|
ThemeConfig.updateTheme(dynamicColor = enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -222,8 +222,6 @@ class MoreSettingsHandlers(
|
|||||||
CardConfig.isCustomDimSet = false
|
CardConfig.isCustomDimSet = false
|
||||||
CardConfig.isCustomBackgroundEnabled = false
|
CardConfig.isCustomBackgroundEnabled = false
|
||||||
saveCardConfig(context)
|
saveCardConfig(context)
|
||||||
|
|
||||||
ThemeConfig.needsResetOnThemeChange = true
|
|
||||||
ThemeConfig.preventBackgroundRefresh = false
|
ThemeConfig.preventBackgroundRefresh = false
|
||||||
|
|
||||||
context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE).edit {
|
context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE).edit {
|
||||||
|
|||||||
Reference in New Issue
Block a user