manager: Rewrite UI state management

This commit is contained in:
ShirkNeko
2025-10-18 22:37:17 +08:00
parent 7b6470cc79
commit 776a753206
4 changed files with 881 additions and 636 deletions

View File

@@ -6,114 +6,187 @@ import androidx.compose.material3.CardDefaults
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Stable
object CardConfig {
// 卡片透明度
var cardAlpha by mutableFloatStateOf(1f)
internal set
// 卡片亮度
var cardDim by mutableFloatStateOf(0f)
internal set
// 卡片阴影
var cardElevation by mutableStateOf(0.dp)
var isShadowEnabled by mutableStateOf(true)
var isCustomAlphaSet by mutableStateOf(false)
var isCustomDimSet by mutableStateOf(false)
var isUserDarkModeEnabled by mutableStateOf(false)
var isUserLightModeEnabled by mutableStateOf(false)
var isCustomBackgroundEnabled by mutableStateOf(false)
internal set
// 功能开关
var isShadowEnabled by mutableStateOf(true)
internal set
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) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val prefs = context.getSharedPreferences("card_settings", Context.MODE_PRIVATE)
prefs.edit().apply {
putFloat("card_alpha", cardAlpha)
putFloat("card_dim", cardDim)
putBoolean("custom_background_enabled", isCustomBackgroundEnabled)
putBoolean("is_shadow_enabled", isShadowEnabled)
putBoolean("is_custom_alpha_set", isCustomAlphaSet)
putBoolean("is_custom_dim_set", isCustomDimSet)
putBoolean("is_user_dark_mode_enabled", isUserDarkModeEnabled)
putBoolean("is_user_light_mode_enabled", isUserLightModeEnabled)
putFloat(Keys.CARD_ALPHA, cardAlpha)
putFloat(Keys.CARD_DIM, cardDim)
putBoolean(Keys.CUSTOM_BACKGROUND_ENABLED, isCustomBackgroundEnabled)
putBoolean(Keys.IS_SHADOW_ENABLED, isShadowEnabled)
putBoolean(Keys.IS_CUSTOM_ALPHA_SET, isCustomAlphaSet)
putBoolean(Keys.IS_CUSTOM_DIM_SET, isCustomDimSet)
putBoolean(Keys.IS_USER_DARK_MODE_ENABLED, isUserDarkModeEnabled)
putBoolean(Keys.IS_USER_LIGHT_MODE_ENABLED, isUserLightModeEnabled)
apply()
}
}
/**
* 从SharedPreferences加载卡片配置
*/
fun load(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
cardAlpha = prefs.getFloat("card_alpha", 1f)
cardDim = prefs.getFloat("card_dim", 0f)
isCustomBackgroundEnabled = prefs.getBoolean("custom_background_enabled", false)
isShadowEnabled = prefs.getBoolean("is_shadow_enabled", true)
isCustomAlphaSet = prefs.getBoolean("is_custom_alpha_set", false)
isCustomDimSet = prefs.getBoolean("is_custom_dim_set", false)
isUserDarkModeEnabled = prefs.getBoolean("is_user_dark_mode_enabled", false)
isUserLightModeEnabled = prefs.getBoolean("is_user_light_mode_enabled", false)
updateShadowEnabled(isShadowEnabled)
val prefs = context.getSharedPreferences("card_settings", Context.MODE_PRIVATE)
cardAlpha = prefs.getFloat(Keys.CARD_ALPHA, 1f).coerceIn(0f, 1f)
cardDim = prefs.getFloat(Keys.CARD_DIM, 0f).coerceIn(0f, 1f)
isCustomBackgroundEnabled = prefs.getBoolean(Keys.CUSTOM_BACKGROUND_ENABLED, false)
isShadowEnabled = prefs.getBoolean(Keys.IS_SHADOW_ENABLED, true)
isCustomAlphaSet = prefs.getBoolean(Keys.IS_CUSTOM_ALPHA_SET, false)
isCustomDimSet = prefs.getBoolean(Keys.IS_CUSTOM_DIM_SET, false)
isUserDarkModeEnabled = prefs.getBoolean(Keys.IS_USER_DARK_MODE_ENABLED, false)
isUserLightModeEnabled = prefs.getBoolean(Keys.IS_USER_LIGHT_MODE_ENABLED, false)
// 应用阴影设置
updateShadow(isShadowEnabled, if (isShadowEnabled) 4.dp else 0.dp)
}
/**
* 更新阴影启用状态
*/
@Deprecated("使用 updateShadow 替代", ReplaceWith("updateShadow(enabled)"))
fun updateShadowEnabled(enabled: Boolean) {
isShadowEnabled = enabled
cardElevation = 0.dp
}
/**
* 设置主题模式默认值
*/
fun setThemeDefaults(isDarkMode: Boolean) {
if (!isCustomAlphaSet) {
cardAlpha = 1f
}
if (!isCustomDimSet) {
cardDim = if (isDarkMode) 0.5f else 0f
}
updateShadowEnabled(isShadowEnabled)
updateShadow(enabled)
}
}
/**
* 获取卡片颜色配置
*/
@Composable
fun getCardColors(originalColor: Color) = CardDefaults.cardColors(
containerColor = originalColor.copy(alpha = CardConfig.cardAlpha),
contentColor = determineContentColor(originalColor)
)
object CardStyleProvider {
/**
* 获取卡片阴影配置
*/
@Composable
fun getCardElevation() = CardDefaults.cardElevation(
defaultElevation = CardConfig.cardElevation,
pressedElevation = CardConfig.cardElevation,
focusedElevation = CardConfig.cardElevation,
hoveredElevation = CardConfig.cardElevation,
draggedElevation = CardConfig.cardElevation,
disabledElevation = CardConfig.cardElevation
)
@Composable
fun getCardColors(originalColor: Color) = CardDefaults.cardColors(
containerColor = originalColor.copy(alpha = CardConfig.cardAlpha),
contentColor = determineContentColor(originalColor),
disabledContainerColor = originalColor.copy(alpha = CardConfig.cardAlpha * 0.38f),
disabledContentColor = determineContentColor(originalColor).copy(alpha = 0.38f)
)
/**
* 根据背景颜色、主题模式和用户设置确定内容颜色
*/
@Composable
private fun determineContentColor(originalColor: Color): Color {
val isDarkTheme = isSystemInDarkTheme()
if (ThemeConfig.isThemeChanging) {
return if (isDarkTheme) Color.White else Color.Black
}
@Composable
fun getCardElevation() = CardDefaults.cardElevation(
defaultElevation = CardConfig.cardElevation,
pressedElevation = if (CardConfig.isShadowEnabled) {
(CardConfig.cardElevation.value + 4).dp
} else 0.dp,
focusedElevation = if (CardConfig.isShadowEnabled) {
(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
)
return when {
CardConfig.isUserLightModeEnabled -> Color.Black
!isDarkTheme && originalColor.luminance() > 0.5f -> Color.Black
isDarkTheme -> Color.White
else -> if (originalColor.luminance() > 0.5f) Color.Black else Color.White
@Composable
private fun determineContentColor(originalColor: Color): Color {
val isDarkTheme = isSystemInDarkTheme()
return when {
ThemeConfig.isThemeChanging -> {
if (isDarkTheme) Color.White else Color.Black
}
CardConfig.isUserLightModeEnabled -> Color.Black
CardConfig.isUserDarkModeEnabled -> Color.White
else -> {
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()

View File

@@ -1,6 +1,5 @@
package com.sukisu.ultra.ui.theme
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.os.Build
@@ -9,9 +8,7 @@ import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
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.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.core.content.edit
import androidx.core.net.toUri
@@ -36,28 +32,31 @@ import coil.compose.AsyncImagePainter
import coil.compose.rememberAsyncImagePainter
import com.sukisu.ultra.ui.theme.util.BackgroundTransformation
import com.sukisu.ultra.ui.theme.util.saveTransformedBackground
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
/**
* 主题配置对象,管理应用的主题相关状态
*/
@Stable
object ThemeConfig {
// 主题状态
var customBackgroundUri by mutableStateOf<Uri?>(null)
var forceDarkMode by mutableStateOf<Boolean?>(null)
var currentTheme by mutableStateOf<ThemeColors>(ThemeColors.Default)
var useDynamicColor by mutableStateOf(false)
// 背景状态
var backgroundImageLoaded by mutableStateOf(false)
var needsResetOnThemeChange by mutableStateOf(false)
var isThemeChanging by mutableStateOf(false)
var preventBackgroundRefresh by mutableStateOf(false)
// 主题变化检测
private var lastDarkModeState: Boolean? = null
fun detectThemeChange(currentDarkMode: Boolean): Boolean {
val isChanged = lastDarkModeState != null && lastDarkModeState != currentDarkMode
val hasChanged = lastDarkModeState != null && lastDarkModeState != currentDarkMode
lastDarkModeState = currentDarkMode
return isChanged
return hasChanged
}
fun resetBackgroundState() {
@@ -66,11 +65,171 @@ object ThemeConfig {
}
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
fun KernelSUTheme(
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
@@ -84,198 +243,223 @@ fun KernelSUTheme(
val context = LocalContext.current
val systemIsDark = isSystemInDarkTheme()
// 检测系统主题变化并保存状态
val themeChanged = ThemeConfig.detectThemeChange(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)
}
// 初始化主题
ThemeInitializer(context = context, systemIsDark = systemIsDark)
// 创建颜色方案
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) createDynamicDarkColorScheme(context) else createDynamicLightColorScheme(context)
}
darkTheme -> createDarkColorScheme()
else -> createLightColorScheme()
}
val colorScheme = createColorScheme(context, darkTheme, dynamicColor)
// 根据暗色模式和自定义背景调整卡片配置
val isDarkModeWithCustomBackground = darkTheme && ThemeConfig.customBackgroundUri != null
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
// 系统栏样式
SystemBarController(darkTheme)
MaterialTheme(
colorScheme = colorScheme,
typography = Typography
) {
Box(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(-2f)
.background(if (darkTheme) if (CardConfig.isCustomBackgroundEnabled) { colorScheme.surfaceContainerLow } else { colorScheme.background }
else if (CardConfig.isCustomBackgroundEnabled) { colorScheme.surfaceContainerLow } else { colorScheme.background })
)
// 自定义背景层
backgroundUri.value?.let {
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(-1f)
.alpha(bgAlpha)
) {
// 背景图片
bgImagePainter?.let { painter ->
Box(
modifier = Modifier
.fillMaxSize()
.paint(
painter = painter,
contentScale = ContentScale.Crop
)
.graphicsLayer {
alpha = (painter.state as? AsyncImagePainter.State.Success)?.let { 1f } ?: 0f
}
)
}
// 亮度调节层 (根据cardDim调整)
Box(
modifier = Modifier
.fillMaxSize()
.background(
if (darkTheme) Color.Black.copy(alpha = 0.6f + dimFactor * 0.3f)
else Color.White.copy(alpha = 0.1f + dimFactor * 0.2f)
)
)
// 边缘渐变遮罩
Box(
modifier = Modifier
.fillMaxSize()
.background(
Brush.radialGradient(
colors = listOf(
Color.Transparent,
if (darkTheme) Color.Black.copy(alpha = 0.5f + dimFactor * 0.2f)
else Color.Black.copy(alpha = 0.2f + dimFactor * 0.1f)
),
radius = 1200f
)
)
)
}
}
// 背景层
BackgroundLayer(darkTheme)
// 内容层
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(1f)
) {
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(
modifier = Modifier
.fillMaxSize()
.zIndex(-2f)
.background(
if (CardConfig.isCustomBackgroundEnabled) {
MaterialTheme.colorScheme.surfaceContainerLow
} else {
MaterialTheme.colorScheme.background
}
)
)
// 自定义背景
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(
modifier = Modifier
.fillMaxSize()
.zIndex(-1f)
.alpha(alpha)
) {
// 背景图片
Box(
modifier = Modifier
.fillMaxSize()
.paint(painter = painter, contentScale = ContentScale.Crop)
.graphicsLayer {
this.alpha = (painter.state as? AsyncImagePainter.State.Success)?.let { 1f } ?: 0f
}
)
// 遮罩层
BackgroundOverlay(darkTheme = darkTheme)
}
}
@Composable
private fun BackgroundOverlay(darkTheme: Boolean) {
val dimFactor = CardConfig.cardDim
// 主要遮罩层
Box(
modifier = Modifier
.fillMaxSize()
.background(
if (darkTheme) {
Color.Black.copy(alpha = 0.3f + dimFactor * 0.4f)
} else {
Color.White.copy(alpha = 0.05f + dimFactor * 0.3f)
}
)
)
// 边缘渐变遮罩
Box(
modifier = Modifier
.fillMaxSize()
.background(
Brush.radialGradient(
colors = listOf(
Color.Transparent,
if (darkTheme) {
Color.Black.copy(alpha = 0.2f + dimFactor * 0.2f)
} else {
Color.Black.copy(alpha = 0.05f + dimFactor * 0.1f)
}
),
radius = 1000f
)
)
)
}
@Composable
private fun createColorScheme(
context: Context,
darkTheme: Boolean,
dynamicColor: Boolean
): ColorScheme {
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)
@Composable
private fun createDynamicDarkColorScheme(context: Context): ColorScheme {
@@ -288,9 +472,6 @@ private fun createDynamicDarkColorScheme(context: Context): ColorScheme {
)
}
/**
* 创建动态浅色颜色方案
*/
@RequiresApi(Build.VERSION_CODES.S)
@Composable
private fun createDynamicLightColorScheme(context: Context): ColorScheme {
@@ -303,11 +484,6 @@ private fun createDynamicLightColorScheme(context: Context): ColorScheme {
)
}
/**
* 创建深色颜色方案
*/
@Composable
private fun createDarkColorScheme() = darkColorScheme(
primary = ThemeConfig.currentTheme.primaryDark,
@@ -347,9 +523,6 @@ private fun createDarkColorScheme() = darkColorScheme(
surfaceContainerHighest = ThemeConfig.currentTheme.surfaceContainerHighestDark,
)
/**
* 创建浅色颜色方案
*/
@Composable
private fun createLightColorScheme() = lightColorScheme(
primary = ThemeConfig.currentTheme.primaryLight,
@@ -389,218 +562,32 @@ private fun createLightColorScheme() = lightColorScheme(
surfaceContainerHighest = ThemeConfig.currentTheme.surfaceContainerHighestLight,
)
/**
* 复制图片到应用内部存储并提升持久性
*/
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
}
}
/**
* 保存并应用自定义背景
*/
// 向后兼容
@OptIn(DelicateCoroutinesApi::class)
fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) {
val finalUri = if (transformation != null) {
saveTransformedBackground(uri, transformation)
} else {
copyImageToInternalStorage(uri)
kotlinx.coroutines.GlobalScope.launch {
BackgroundManager.saveAndApplyCustomBackground(this@saveAndApplyCustomBackground, uri, transformation)
}
// 保存到配置文件
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?) {
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) {
CardConfig.cardElevation = 0.dp
CardConfig.isCustomBackgroundEnabled = true
saveAndApplyCustomBackground(uri)
} 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?) {
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit {
putString(
"theme_mode", when (forceDark) {
true -> "dark"
false -> "light"
null -> "system"
}
)
}
ThemeConfig.forceDarkMode = forceDark
ThemeConfig.needsResetOnThemeChange = forceDark == null
ThemeManager.saveThemeMode(this, forceDark)
}
/**
* 加载主题模式
*/
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) {
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit {
putString("theme_colors", themeName)
}
ThemeConfig.currentTheme = ThemeColors.fromName(themeName)
ThemeManager.saveThemeColors(this, 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) {
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.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(),
)
}
)
}
ThemeManager.saveDynamicColorState(this, enabled)
}

View File

@@ -1,24 +1,23 @@
package com.sukisu.ultra.ui.theme.component
import android.net.Uri
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Fullscreen
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
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.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
@@ -44,48 +43,62 @@ fun ImageEditorDialog(
onDismiss: () -> Unit,
onConfirm: (Uri) -> Unit
) {
var scale by remember { mutableFloatStateOf(1f) }
var offsetX by remember { mutableFloatStateOf(0f) }
var offsetY by remember { mutableFloatStateOf(0f) }
// 图像变换状态
val transformState = remember { ImageTransformState() }
val context = LocalContext.current
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 screenSize by remember { mutableStateOf(Size.Zero) }
// 动画状态
val animationSpec = spring<Float>(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMedium
)
val animatedScale by animateFloatAsState(
targetValue = scale,
targetValue = transformState.scale,
animationSpec = animationSpec,
label = "ScaleAnimation"
)
val animatedOffsetX by animateFloatAsState(
targetValue = offsetX,
targetValue = transformState.offsetX,
animationSpec = animationSpec,
label = "OffsetXAnimation"
)
val animatedOffsetY by animateFloatAsState(
targetValue = offsetY,
targetValue = transformState.offsetY,
animationSpec = animationSpec,
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 {
{
if (imageSize.height > 0 && screenSize.height > 0) {
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,124 +114,298 @@ fun ImageEditorDialog(
Box(
modifier = Modifier
.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 ->
screenSize = Size(size.width.toFloat(), size.height.toFloat())
}
) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(imageUri)
.crossfade(true)
.build(),
contentDescription = stringResource(R.string.settings_custom_background),
contentScale = ContentScale.Fit,
modifier = Modifier
.fillMaxSize()
.graphicsLayer(
scaleX = animatedScale,
scaleY = animatedScale,
translationX = animatedOffsetX,
translationY = animatedOffsetY
)
.pointerInput(Unit) {
detectTransformGestures { _, pan, zoom, _ ->
scope.launch {
try {
val newScale = (scale * zoom).coerceIn(0.5f, 3f)
val maxOffsetX = max(0f, size.width * (newScale - 1) / 2)
val maxOffsetY = max(0f, size.height * (newScale - 1) / 2)
val newOffsetX = if (maxOffsetX > 0) {
(offsetX + pan.x).coerceIn(-maxOffsetX, maxOffsetX)
} else {
0f
}
val newOffsetY = if (maxOffsetY > 0) {
(offsetY + pan.y).coerceIn(-maxOffsetY, maxOffsetY)
} else {
0f
}
updateTransformation(newScale, newOffsetX, newOffsetY)
} catch (_: Exception) {
updateTransformation(lastScale, lastOffsetX, lastOffsetY)
}
}
}
}
.onSizeChanged { size ->
imageSize = Size(size.width.toFloat(), size.height.toFloat())
}
// 图像显示区域
ImageDisplayArea(
imageUri = imageUri,
animatedScale = animatedScale,
animatedOffsetX = animatedOffsetX,
animatedOffsetY = animatedOffsetY,
transformState = transformState,
onImageSizeChanged = { imageSize = it },
modifier = Modifier.fillMaxSize()
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.align(Alignment.TopCenter),
horizontalArrangement = Arrangement.SpaceBetween
) {
IconButton(
onClick = onDismiss,
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.background(Color.Black.copy(alpha = 0.6f))
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.cancel),
tint = Color.White
)
}
IconButton(
onClick = { scaleToFullScreen() },
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.background(Color.Black.copy(alpha = 0.6f))
) {
Icon(
imageVector = Icons.Default.Fullscreen,
contentDescription = stringResource(R.string.reprovision),
tint = Color.White
)
}
IconButton(
onClick = {
scope.launch {
try {
val transformation = BackgroundTransformation(scale, offsetX, offsetY)
val savedUri = context.saveTransformedBackground(imageUri, transformation)
savedUri?.let { onConfirm(it) }
} catch (_: Exception) {
""
}
}
},
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.background(Color.Black.copy(alpha = 0.6f))
) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = stringResource(R.string.confirm),
tint = Color.White
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clip(RoundedCornerShape(8.dp))
.background(Color.Black.copy(alpha = 0.6f))
.padding(16.dp)
.align(Alignment.BottomCenter)
) {
Text(
text = stringResource(id = R.string.image_editor_hint),
color = Color.White,
style = MaterialTheme.typography.bodyMedium
)
}
// 顶部工具栏
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(
model = ImageRequest.Builder(LocalContext.current)
.data(imageUri)
.crossfade(true)
.build(),
contentDescription = stringResource(R.string.settings_custom_background),
contentScale = ContentScale.Fit,
modifier = modifier
.graphicsLayer(
scaleX = animatedScale,
scaleY = animatedScale,
translationX = animatedOffsetX,
translationY = animatedOffsetY
)
.pointerInput(Unit) {
detectTransformGestures { _, pan, zoom, _ ->
scope.launch {
try {
val newScale = (transformState.scale * zoom).coerceIn(0.5f, 3f)
val maxOffsetX = max(0f, size.width * (newScale - 1) / 2)
val maxOffsetY = max(0f, size.height * (newScale - 1) / 2)
val newOffsetX = if (maxOffsetX > 0) {
(transformState.offsetX + pan.x).coerceIn(-maxOffsetX, maxOffsetX)
} else 0f
val newOffsetY = if (maxOffsetY > 0) {
(transformState.offsetY + pan.y).coerceIn(-maxOffsetY, maxOffsetY)
} else 0f
transformState.updateTransform(newScale, newOffsetX, newOffsetY)
} catch (_: Exception) {
transformState.resetToLast()
}
}
}
}
.onSizeChanged { size ->
onImageSizeChanged(Size(size.width.toFloat(), size.height.toFloat()))
}
)
}
/**
* 顶部工具栏组件
*/
@Composable
private fun TopToolbar(
onDismiss: () -> Unit,
onFullscreen: () -> Unit,
onConfirm: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(24.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
// 关闭按钮
ActionButton(
onClick = onDismiss,
icon = Icons.Default.Close,
contentDescription = stringResource(R.string.cancel),
backgroundColor = MaterialTheme.colorScheme.error.copy(alpha = 0.9f)
)
// 全屏按钮
ActionButton(
onClick = onFullscreen,
icon = Icons.Default.Fullscreen,
contentDescription = stringResource(R.string.reprovision),
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)
)
}
}
/**
* 操作按钮组件
*/
@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 = {
isPressed = true
onClick()
},
modifier = modifier
.size(64.dp)
.graphicsLayer(
scaleX = buttonScale,
scaleY = buttonScale,
alpha = buttonAlpha
),
shape = CircleShape,
color = backgroundColor,
shadowElevation = 8.dp
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Icon(
imageVector = icon,
contentDescription = contentDescription,
tint = Color.White,
modifier = Modifier.size(28.dp)
)
}
}
LaunchedEffect(isPressed) {
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()
.padding(24.dp)
.alpha(cardAlpha)
.graphicsLayer {
translationY = cardTranslationY
},
colors = CardDefaults.cardColors(
containerColor = Color.Black.copy(alpha = 0.85f)
),
shape = RoundedCornerShape(16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 12.dp)
) {
Text(
text = stringResource(id = R.string.image_editor_hint),
color = Color.White,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.padding(20.dp)
.fillMaxWidth()
)
}
}

View File

@@ -105,26 +105,24 @@ class MoreSettingsHandlers(
else -> null
}
context.saveThemeMode(newThemeMode)
ThemeConfig.updateTheme(darkMode = newThemeMode)
when (index) {
2 -> { // 深色
ThemeConfig.forceDarkMode = true
CardConfig.isUserDarkModeEnabled = true
CardConfig.isUserLightModeEnabled = false
ThemeConfig.updateTheme(darkMode = true)
CardConfig.updateThemePreference(darkMode = true, lightMode = false)
CardConfig.setThemeDefaults(true)
CardConfig.save(context)
}
1 -> { // 浅色
ThemeConfig.forceDarkMode = false
CardConfig.isUserLightModeEnabled = true
CardConfig.isUserDarkModeEnabled = false
ThemeConfig.updateTheme(darkMode = false)
CardConfig.updateThemePreference(darkMode = false, lightMode = true)
CardConfig.setThemeDefaults(false)
CardConfig.save(context)
}
0 -> { // 跟随系统
ThemeConfig.forceDarkMode = null
CardConfig.isUserLightModeEnabled = false
CardConfig.isUserDarkModeEnabled = false
ThemeConfig.updateTheme(darkMode = null)
CardConfig.updateThemePreference(darkMode = null, lightMode = null)
val isNightModeActive = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
CardConfig.setThemeDefaults(isNightModeActive)
CardConfig.save(context)
@@ -145,6 +143,7 @@ class MoreSettingsHandlers(
ThemeColors.Yellow -> "yellow"
else -> "default"
})
ThemeConfig.updateTheme(theme = theme)
}
/**
@@ -153,6 +152,7 @@ class MoreSettingsHandlers(
fun handleDynamicColorChange(enabled: Boolean) {
state.useDynamicColor = enabled
context.saveDynamicColorState(enabled)
ThemeConfig.updateTheme(dynamicColor = enabled)
}
/**
@@ -222,8 +222,6 @@ class MoreSettingsHandlers(
CardConfig.isCustomDimSet = false
CardConfig.isCustomBackgroundEnabled = false
saveCardConfig(context)
ThemeConfig.needsResetOnThemeChange = true
ThemeConfig.preventBackgroundRefresh = false
context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE).edit {