diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/CardManage.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/CardManage.kt index e05ce3ba..c827d9ef 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/CardManage.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/CardManage.kt @@ -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() diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Theme.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Theme.kt index 6325c4c2..87ac86d8 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Theme.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/Theme.kt @@ -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(null) var forceDarkMode by mutableStateOf(null) var currentTheme by mutableStateOf(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) } \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/component/ImageEditorDialog.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/component/ImageEditorDialog.kt index 408c78bf..803d1f04 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/theme/component/ImageEditorDialog.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/theme/component/ImageEditorDialog.kt @@ -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( + 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) + ) } } -} \ No newline at end of file +} + +/** + * 图像变换状态管理类 + */ +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() + ) + } +} diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/MoreSettingsHandlers.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/MoreSettingsHandlers.kt index 3ca2fda9..355a759b 100644 --- a/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/MoreSettingsHandlers.kt +++ b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/moreSettings/MoreSettingsHandlers.kt @@ -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 {