diff --git a/app/src/main/java/com/inspection/camera/data/PreferencesManager.kt b/app/src/main/java/com/inspection/camera/data/PreferencesManager.kt index df1500f..ead0254 100644 --- a/app/src/main/java/com/inspection/camera/data/PreferencesManager.kt +++ b/app/src/main/java/com/inspection/camera/data/PreferencesManager.kt @@ -11,6 +11,7 @@ import androidx.datastore.preferences.preferencesDataStore import com.inspection.camera.data.models.ImageQuality import com.inspection.camera.data.models.LocationMode import com.inspection.camera.data.models.MergeLayoutType +import com.inspection.camera.data.models.ThemeMode import com.inspection.camera.data.models.WatermarkStyle import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -34,6 +35,7 @@ class PreferencesManager(private val context: Context) { private val KEY_MERGE_CONTENT = stringPreferencesKey("merge_content") private val KEY_RECORDER_NAME = stringPreferencesKey("recorder_name") private val KEY_FILE_NAME_TEMPLATE = stringPreferencesKey("file_name_template") + private val KEY_THEME_MODE = stringPreferencesKey("theme_mode") } val watermarkStyle: Flow = context.dataStore.data.map { prefs -> @@ -109,6 +111,15 @@ class PreferencesManager(private val context: Context) { prefs[KEY_FILE_NAME_TEMPLATE] ?: "{project}_{inspector}_{date}" } + val themeMode: Flow = context.dataStore.data.map { prefs -> + val mode = prefs[KEY_THEME_MODE] ?: ThemeMode.FOLLOW_SYSTEM.name + try { + ThemeMode.valueOf(mode) + } catch (e: Exception) { + ThemeMode.FOLLOW_SYSTEM + } + } + suspend fun setWatermarkStyle(style: WatermarkStyle) { context.dataStore.edit { prefs -> prefs[KEY_WATERMARK_STYLE] = style.name @@ -180,4 +191,10 @@ class PreferencesManager(private val context: Context) { prefs[KEY_FILE_NAME_TEMPLATE] = template } } + + suspend fun setThemeMode(mode: ThemeMode) { + context.dataStore.edit { prefs -> + prefs[KEY_THEME_MODE] = mode.name + } + } } diff --git a/app/src/main/java/com/inspection/camera/data/models/WatermarkModels.kt b/app/src/main/java/com/inspection/camera/data/models/WatermarkModels.kt index 8aefb0d..cfb8d4e 100644 --- a/app/src/main/java/com/inspection/camera/data/models/WatermarkModels.kt +++ b/app/src/main/java/com/inspection/camera/data/models/WatermarkModels.kt @@ -64,3 +64,9 @@ enum class MergeLayoutType(val rows: Int, val cols: Int, val displayName: String Grid2x2(2, 2, "2x2网格", 4), Grid3x3(3, 3, "3x3网格", 9) } + +enum class ThemeMode { + FOLLOW_SYSTEM, + LIGHT, + DARK +} diff --git a/app/src/main/java/com/inspection/camera/ui/MainActivity.kt b/app/src/main/java/com/inspection/camera/ui/MainActivity.kt index 99831a8..04d7604 100644 --- a/app/src/main/java/com/inspection/camera/ui/MainActivity.kt +++ b/app/src/main/java/com/inspection/camera/ui/MainActivity.kt @@ -17,6 +17,7 @@ import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -31,6 +32,7 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.inspection.camera.data.PreferencesManager +import com.inspection.camera.data.models.ThemeMode import com.inspection.camera.ui.camera.CameraScreen import com.inspection.camera.ui.gallery.GalleryScreen import com.inspection.camera.ui.merge.MergeScreen @@ -47,7 +49,13 @@ class MainActivity : ComponentActivity() { preferencesManager = PreferencesManager(this) setContent { - InspectionCameraTheme { + var themeMode by remember { mutableStateOf(ThemeMode.FOLLOW_SYSTEM) } + + LaunchedEffect(Unit) { + preferencesManager.themeMode.collect { themeMode = it } + } + + InspectionCameraTheme(themeMode = themeMode) { MainApp(preferencesManager = preferencesManager) } } diff --git a/app/src/main/java/com/inspection/camera/ui/gallery/GalleryScreen.kt b/app/src/main/java/com/inspection/camera/ui/gallery/GalleryScreen.kt index da8ae64..70402be 100644 --- a/app/src/main/java/com/inspection/camera/ui/gallery/GalleryScreen.kt +++ b/app/src/main/java/com/inspection/camera/ui/gallery/GalleryScreen.kt @@ -58,7 +58,6 @@ import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner import coil.compose.AsyncImage import coil.request.ImageRequest -import com.inspection.camera.ui.theme.Primary import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -130,10 +129,10 @@ fun GalleryScreen(onNavigateBack: () -> Unit) { } }, colors = TopAppBarDefaults.topAppBarColors( - containerColor = Primary, - titleContentColor = Color.White, - navigationIconContentColor = Color.White, - actionIconContentColor = Color.White + containerColor = MaterialTheme.colorScheme.primary, + titleContentColor = MaterialTheme.colorScheme.onPrimary, + navigationIconContentColor = MaterialTheme.colorScheme.onPrimary, + actionIconContentColor = MaterialTheme.colorScheme.onPrimary ) ) } @@ -168,7 +167,7 @@ fun GalleryScreen(onNavigateBack: () -> Unit) { Box( modifier = Modifier.weight(1f).aspectRatio(1f) .clip(RoundedCornerShape(8.dp)) - .background(Color.LightGray) + .background(MaterialTheme.colorScheme.surfaceVariant) .clickable { selectedImageUri = img.uri } @@ -236,7 +235,7 @@ fun GalleryScreen(onNavigateBack: () -> Unit) { Text("图片预览") Row { IconButton(onClick = { showDeleteConfirm = true }) { - Icon(Icons.Default.Delete, contentDescription = "删除", tint = Color.Red) + Icon(Icons.Default.Delete, contentDescription = "删除", tint = MaterialTheme.colorScheme.error) } IconButton(onClick = { selectedImageUri = null }) { Icon(Icons.Default.Close, contentDescription = "关闭") @@ -271,7 +270,7 @@ fun GalleryScreen(onNavigateBack: () -> Unit) { showDeleteConfirm = false } }) { - Text("确定删除", color = Color.Red) + Text("确定删除", color = MaterialTheme.colorScheme.error) } }, dismissButton = { @@ -293,7 +292,7 @@ private fun ZoomableImage(imageUri: Uri, modifier: Modifier = Modifier) { Box( modifier = modifier .clip(RoundedCornerShape(8.dp)) - .background(Color.Black.copy(alpha = 0.05f)) + .background(MaterialTheme.colorScheme.surfaceVariant) .pointerInput(Unit) { detectTransformGestures { _, pan, zoom, _ -> scale = (scale * zoom).coerceIn(1f, 5f) diff --git a/app/src/main/java/com/inspection/camera/ui/merge/MergeScreen.kt b/app/src/main/java/com/inspection/camera/ui/merge/MergeScreen.kt index e0cff9d..8456292 100644 --- a/app/src/main/java/com/inspection/camera/ui/merge/MergeScreen.kt +++ b/app/src/main/java/com/inspection/camera/ui/merge/MergeScreen.kt @@ -72,7 +72,6 @@ import com.inspection.camera.data.models.ImageItem import com.inspection.camera.data.models.ImageQuality import com.inspection.camera.data.models.MergeLayoutType import com.inspection.camera.data.models.WatermarkStyle -import com.inspection.camera.ui.theme.Primary import com.inspection.camera.util.ImageProcessor import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -292,9 +291,9 @@ fun MergeScreen( } }, colors = TopAppBarDefaults.topAppBarColors( - containerColor = Primary, - titleContentColor = Color.White, - navigationIconContentColor = Color.White + containerColor = MaterialTheme.colorScheme.primary, + titleContentColor = MaterialTheme.colorScheme.onPrimary, + navigationIconContentColor = MaterialTheme.colorScheme.onPrimary ) ) } @@ -341,7 +340,7 @@ fun MergeScreen( Text( text = autoImportText, style = MaterialTheme.typography.bodyMedium, - color = Primary, + color = MaterialTheme.colorScheme.primary, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 12.dp) @@ -365,13 +364,13 @@ fun MergeScreen( Box( modifier = Modifier .clip(RoundedCornerShape(4.dp)) - .background(if (imageQuality == quality) Primary else Color.LightGray) + .background(if (imageQuality == quality) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant) .clickable { imageQuality = quality } .padding(horizontal = 12.dp, vertical = 4.dp) ) { Text( text = quality.displayName, - color = if (imageQuality == quality) Color.White else Color.Black, + color = if (imageQuality == quality) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodySmall ) } @@ -403,7 +402,7 @@ fun MergeScreen( .weight(1f) .aspectRatio(1f) .clip(RoundedCornerShape(8.dp)) - .background(Color.LightGray) + .background(MaterialTheme.colorScheme.surfaceVariant) .then( if (index < images.size) { Modifier.clickable { @@ -435,12 +434,12 @@ fun MergeScreen( modifier = Modifier .align(Alignment.TopEnd) .size(32.dp) - .background(Color.Black.copy(alpha = 0.5f), CircleShape) + .background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.5f), CircleShape) ) { Icon( Icons.Default.Close, contentDescription = "删除", - tint = Color.White, + tint = MaterialTheme.colorScheme.onPrimary, modifier = Modifier.size(16.dp) ) } @@ -448,7 +447,7 @@ fun MergeScreen( Icon( Icons.Default.Add, contentDescription = "添加图片", - tint = Color.Gray, + tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(48.dp) ) } @@ -633,20 +632,20 @@ private fun LayoutOption( Box( modifier = Modifier .clip(RoundedCornerShape(8.dp)) - .background(if (isSelected) Primary else Color.LightGray) + .background(if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant) .clickable(onClick = onClick) .padding(12.dp) - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = displayText, - color = if (isSelected) Color.White else Color.Black + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = displayText, + color = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface ) if (isSelected) { - Icon( - Icons.Default.Check, - contentDescription = null, - tint = Color.White, + Icon( + Icons.Default.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.onPrimary, modifier = Modifier.size(16.dp) ) } diff --git a/app/src/main/java/com/inspection/camera/ui/settings/SettingsScreen.kt b/app/src/main/java/com/inspection/camera/ui/settings/SettingsScreen.kt index 62bcfba..76e7ea3 100644 --- a/app/src/main/java/com/inspection/camera/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/inspection/camera/ui/settings/SettingsScreen.kt @@ -35,14 +35,13 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.inspection.camera.data.PreferencesManager import com.inspection.camera.data.models.ImageQuality import com.inspection.camera.data.models.LocationMode import com.inspection.camera.data.models.MergeLayoutType +import com.inspection.camera.data.models.ThemeMode import com.inspection.camera.data.models.WatermarkStyle -import com.inspection.camera.ui.theme.Primary import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -61,6 +60,7 @@ fun SettingsScreen( var defaultTheme by remember { mutableStateOf("") } var recorderName by remember { mutableStateOf("") } var fileNameTemplate by remember { mutableStateOf("{project}_{inspector}_{date}") } + var themeMode by remember { mutableStateOf(ThemeMode.FOLLOW_SYSTEM) } // 加载配置 scope.launch { @@ -87,6 +87,9 @@ scope.launch { scope.launch { preferencesManager.fileNameTemplate.collect { fileNameTemplate = it } } +scope.launch { + preferencesManager.themeMode.collect { themeMode = it } +} Scaffold( topBar = { @@ -114,13 +117,13 @@ scope.launch { // 返回主界面 onNavigateBack() }) { - Text("保存", color = Color.White) + Text("保存", color = MaterialTheme.colorScheme.onPrimary) } }, colors = TopAppBarDefaults.topAppBarColors( - containerColor = Primary, - titleContentColor = Color.White, - navigationIconContentColor = Color.White + containerColor = MaterialTheme.colorScheme.primary, + titleContentColor = MaterialTheme.colorScheme.onPrimary, + navigationIconContentColor = MaterialTheme.colorScheme.onPrimary ) ) } @@ -157,7 +160,7 @@ scope.launch { else -> "" }, style = MaterialTheme.typography.bodySmall, - color = Color.Gray, + color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(start = 40.dp) ) } @@ -271,7 +274,7 @@ scope.launch { else -> "" }, style = MaterialTheme.typography.bodySmall, - color = Color.Gray, + color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(start = 40.dp) ) } @@ -281,6 +284,34 @@ scope.launch { Spacer(modifier = Modifier.height(16.dp)) + // 主题设置 + SettingsSection(title = "主题设置") { + SettingsItem(title = "界面主题") { + listOf( + ThemeMode.FOLLOW_SYSTEM to "跟随系统", + ThemeMode.LIGHT to "浅色模式", + ThemeMode.DARK to "暗色模式" + ).forEach { (mode, label) -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { scope.launch { preferencesManager.setThemeMode(mode) } } + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = themeMode == mode, + onClick = { scope.launch { preferencesManager.setThemeMode(mode) } } + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(label) + } + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + // 通用设置 SettingsSection(title = "通用设置") { SettingsItem(title = "默认拼图标题") { @@ -321,7 +352,7 @@ SettingsItem(title = "记录人信息") { Text( text = "可用占位符:{project} {device} {inspector} {date} {time}", style = MaterialTheme.typography.bodySmall, - color = Color.Gray + color = MaterialTheme.colorScheme.onSurfaceVariant ) } } @@ -349,13 +380,13 @@ private fun SettingsSection( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), - colors = CardDefaults.cardColors(containerColor = Color.White) + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) ) { Column(modifier = Modifier.padding(16.dp)) { Text( text = title, style = MaterialTheme.typography.titleMedium, - color = Primary + color = MaterialTheme.colorScheme.primary ) Spacer(modifier = Modifier.height(8.dp)) content() diff --git a/app/src/main/java/com/inspection/camera/ui/theme/Theme.kt b/app/src/main/java/com/inspection/camera/ui/theme/Theme.kt index 9df8972..5c6f10a 100644 --- a/app/src/main/java/com/inspection/camera/ui/theme/Theme.kt +++ b/app/src/main/java/com/inspection/camera/ui/theme/Theme.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.SideEffect import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat +import com.inspection.camera.data.models.ThemeMode private val LightColorScheme = lightColorScheme( primary = Primary, @@ -37,10 +38,15 @@ private val DarkColorScheme = darkColorScheme( @Composable fun InspectionCameraTheme( - darkTheme: Boolean = isSystemInDarkTheme(), + themeMode: ThemeMode = ThemeMode.FOLLOW_SYSTEM, content: @Composable () -> Unit ) { - val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme + val isDark = when (themeMode) { + ThemeMode.FOLLOW_SYSTEM -> isSystemInDarkTheme() + ThemeMode.LIGHT -> false + ThemeMode.DARK -> true + } + val colorScheme = if (isDark) DarkColorScheme else LightColorScheme val view = LocalView.current if (!view.isInEditMode) { diff --git a/docs/superpowers/plans/2026-05-13-theme-mode-plan.md b/docs/superpowers/plans/2026-05-13-theme-mode-plan.md new file mode 100644 index 0000000..d7533f0 --- /dev/null +++ b/docs/superpowers/plans/2026-05-13-theme-mode-plan.md @@ -0,0 +1,519 @@ +# 主题模式切换 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add theme mode toggle (follow-system/light/dark) and fix dark mode font visibility + +**Architecture:** Add ThemeMode enum + PreferencesManager storage, thread themeMode through InspectionCameraTheme, replace all hardcoded Color values with MaterialTheme.colorScheme.* across all screens + +**Tech Stack:** Jetpack Compose, Material3, DataStore Preferences + +--- + +### Task 1: Add ThemeMode enum + +**Files:** +- Modify: `app/src/main/java/com/inspection/camera/data/models/WatermarkModels.kt` + +- [ ] **Step 1: Add ThemeMode enum** + +Add after the existing enums at end of file: + +```kotlin +enum class ThemeMode { + FOLLOW_SYSTEM, + LIGHT, + DARK +} +``` + +- [ ] **Step 2: Verify compilation** + +Run: `cd android-CheckShot && ./gradlew :app:compileDebugKotlin` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add app/src/main/java/com/inspection/camera/data/models/WatermarkModels.kt +git commit -m "feat: add ThemeMode enum (follow-system/light/dark)" +``` + +--- + +### Task 2: Add themeMode to PreferencesManager + +**Files:** +- Modify: `app/src/main/java/com/inspection/camera/data/PreferencesManager.kt` + +- [ ] **Step 1: Add KEY_THEME_MODE + themeMode Flow + setter** + +After `KEY_FILE_NAME_TEMPLATE` line, add: +```kotlin +private val KEY_THEME_MODE = stringPreferencesKey("theme_mode") +``` + +After `fileNameTemplate` Flow, add: +```kotlin +val themeMode: Flow = context.dataStore.data.map { prefs -> + val mode = prefs[KEY_THEME_MODE] ?: ThemeMode.FOLLOW_SYSTEM.name + try { + ThemeMode.valueOf(mode) + } catch (e: Exception) { + ThemeMode.FOLLOW_SYSTEM + } +} +``` + +After `setFileNameTemplate`, add: +```kotlin +suspend fun setThemeMode(mode: ThemeMode) { + context.dataStore.edit { prefs -> + prefs[KEY_THEME_MODE] = mode.name + } +} +``` + +Add import for ThemeMode above existing imports: +```kotlin +import com.inspection.camera.data.models.ThemeMode +``` + +- [ ] **Step 2: Verify compilation** + +Run: `cd android-CheckShot && ./gradlew :app:compileDebugKotlin` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add app/src/main/java/com/inspection/camera/data/PreferencesManager.kt +git commit -m "feat: add themeMode preference to DataStore" +``` + +--- + +### Task 3: Update InspectionCameraTheme to accept themeMode + +**Files:** +- Modify: `app/src/main/java/com/inspection/camera/ui/theme/Theme.kt` + +- [ ] **Step 1: Replace darkTheme param with themeMode** + +Change the function signature from: +```kotlin +fun InspectionCameraTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) +``` +to: +```kotlin +fun InspectionCameraTheme( + themeMode: ThemeMode = ThemeMode.FOLLOW_SYSTEM, + content: @Composable () -> Unit +) { + val isDark = when (themeMode) { + ThemeMode.FOLLOW_SYSTEM -> isSystemInDarkTheme() + ThemeMode.LIGHT -> false + ThemeMode.DARK -> true + } + val colorScheme = if (isDark) DarkColorScheme else LightColorScheme + // ... rest stays the same +} +``` + +Also remove the old `darkTheme: Boolean = isSystemInDarkTheme()` parameter and the `val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme` line since it's now inside the function. + +Add import: +```kotlin +import com.inspection.camera.data.models.ThemeMode +``` + +The final function should look like: +```kotlin +@Composable +fun InspectionCameraTheme( + themeMode: ThemeMode = ThemeMode.FOLLOW_SYSTEM, + content: @Composable () -> Unit +) { + val isDark = when (themeMode) { + ThemeMode.FOLLOW_SYSTEM -> isSystemInDarkTheme() + ThemeMode.LIGHT -> false + ThemeMode.DARK -> true + } + val colorScheme = if (isDark) DarkColorScheme else LightColorScheme + // ... rest unchanged +} +``` + +- [ ] **Step 2: Verify compilation** + +Run: `cd android-CheckShot && ./gradlew :app:compileDebugKotlin` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add app/src/main/java/com/inspection/camera/ui/theme/Theme.kt +git commit -m "refactor: replace darkTheme param with themeMode in InspectionCameraTheme" +``` + +--- + +### Task 4: Update MainActivity to pass themeMode + +**Files:** +- Modify: `app/src/main/java/com/inspection/camera/ui/MainActivity.kt` + +- [ ] **Step 1: Read themeMode from PreferencesManager and pass to theme** + +Add `var themeMode` state variable and collect the Flow. Pass to `InspectionCameraTheme`: + +```kotlin +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import com.inspection.camera.data.models.ThemeMode + +class MainActivity : ComponentActivity() { + private lateinit var preferencesManager: PreferencesManager + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + + preferencesManager = PreferencesManager(this) + + setContent { + var themeMode by remember { mutableStateOf(ThemeMode.FOLLOW_SYSTEM) } + + LaunchedEffect(Unit) { + preferencesManager.themeMode.collect { themeMode = it } + } + + InspectionCameraTheme(themeMode = themeMode) { + MainApp(preferencesManager = preferencesManager) + } + } + } +} +``` + +Remove `import androidx.compose.foundation.isSystemInDarkTheme` if it was previously used only through `InspectionCameraTheme`'s default. + +- [ ] **Step 2: Verify compilation** + +Run: `cd android-CheckShot && ./gradlew :app:compileDebugKotlin` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add app/src/main/java/com/inspection/camera/ui/MainActivity.kt +git commit -m "feat: read themeMode preference and pass to InspectionCameraTheme" +``` + +--- + +### Task 5: Add theme selector to SettingsScreen + +**Files:** +- Modify: `app/src/main/java/com/inspection/camera/ui/settings/SettingsScreen.kt` + +- [ ] **Step 1: Add themeMode state and collect from preferences** + +Add state variable alongside existing ones: +```kotlin +var themeMode by remember { mutableStateOf(ThemeMode.FOLLOW_SYSTEM) } +``` + +Add collection logic: +```kotlin +scope.launch { + preferencesManager.themeMode.collect { themeMode = it } +} +``` + +- [ ] **Step 2: Add theme section UI before "通用设置" section** + +Add before the "通用设置" section (before `Spacer(modifier = Modifier.height(16.dp))` that precedes `SettingsSection(title = "通用设置")`): + +```kotlin +Spacer(modifier = Modifier.height(16.dp)) + +// 主题设置 +SettingsSection(title = "主题设置") { + SettingsItem(title = "界面主题") { + listOf( + ThemeMode.FOLLOW_SYSTEM to "跟随系统", + ThemeMode.LIGHT to "浅色模式", + ThemeMode.DARK to "暗色模式" + ).forEach { (mode, label) -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { scope.launch { preferencesManager.setThemeMode(mode) } } + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = themeMode == mode, + onClick = { scope.launch { preferencesManager.setThemeMode(mode) } } + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(label) + } + } + } +} +``` + +Replace hardcoded settings colors. Change `CardDefaults.cardColors(containerColor = Color.White)` (line 352): +```kotlin +colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) +``` + +Change section title `Primary` (line 358) to `MaterialTheme.colorScheme.primary`. + +Note: TopAppBar colors, description text colors, etc. will be fixed in Task 7 (global pass). + +Add import: +```kotlin +import com.inspection.camera.data.models.ThemeMode +import androidx.compose.material3.MaterialTheme +``` + +- [ ] **Step 3: Verify compilation** + +Run: `cd android-CheckShot && ./gradlew :app:compileDebugKotlin` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 4: Commit** + +```bash +git add app/src/main/java/com/inspection/camera/ui/settings/SettingsScreen.kt +git commit -m "feat: add theme selector UI to settings screen" +``` + +--- + +### Task 6: Replace hardcoded colors in GalleryScreen + +**Files:** +- Modify: `app/src/main/java/com/inspection/camera/ui/gallery/GalleryScreen.kt` + +- [ ] **Step 1: Replace color values** + +Replace TopAppBar colors (lines 132-137): +```kotlin +colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primary, + titleContentColor = MaterialTheme.colorScheme.onPrimary, + navigationIconContentColor = MaterialTheme.colorScheme.onPrimary, + actionIconContentColor = MaterialTheme.colorScheme.onPrimary +) +``` + +Replace `Color.LightGray` placeholder background (line 171): +```kotlin +.background(MaterialTheme.colorScheme.surfaceVariant) +``` + +Replace `Color.Red` delete icon tint (line 239): +```kotlin +Icon(Icons.Default.Delete, contentDescription = "删除", tint = MaterialTheme.colorScheme.error) +``` + +Replace `Color.Red` delete button text (line 274): +```kotlin +Text("确定删除", color = MaterialTheme.colorScheme.error) +``` + +Replace `Color.Black.copy(alpha = 0.05f)` zoomable background (line 296): +```kotlin +.background(MaterialTheme.colorScheme.surfaceVariant) +``` + +Remove unused imports: `import com.inspection.camera.ui.theme.Primary` + +- [ ] **Step 2: Verify compilation** + +Run: `cd android-CheckShot && ./gradlew :app:compileDebugKotlin` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add app/src/main/java/com/inspection/camera/ui/gallery/GalleryScreen.kt +git commit -m "fix: replace hardcoded colors with MaterialTheme.colorScheme in GalleryScreen" +``` + +--- + +### Task 7: Replace hardcoded colors in MergeScreen + +**Files:** +- Modify: `app/src/main/java/com/inspection/camera/ui/merge/MergeScreen.kt` + +- [ ] **Step 1: Replace color values** + +TopAppBar colors (lines 294-298): +```kotlin +colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primary, + titleContentColor = MaterialTheme.colorScheme.onPrimary, + navigationIconContentColor = MaterialTheme.colorScheme.onPrimary +) +``` + +Auto-import text color (line 344): +```kotlin +color = MaterialTheme.colorScheme.primary +``` + +Quality/layout button backgrounds (lines 368, 636): +```kotlin +.background(if (imageQuality == quality) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant) +``` + +Quality button text (line 374): +```kotlin +color = if (imageQuality == quality) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface +``` + +Placeholder background (line 406): +```kotlin +.background(MaterialTheme.colorScheme.surfaceVariant) +``` + +Selection overlay (line 438): +```kotlin +.background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.5f), CircleShape) +``` + +Check icon tint (lines 443, 649): +```kotlin +tint = MaterialTheme.colorScheme.onPrimary +``` + +Add icon tint (line 451): +```kotlin +tint = MaterialTheme.colorScheme.onSurfaceVariant +``` + +Layout option backgrounds (line 636): +```kotlin +.background(if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant) +``` + +Layout option text (line 643): +```kotlin +color = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface +``` + +Remove unused import: `import com.inspection.camera.ui.theme.Primary` + +- [ ] **Step 2: Verify compilation** + +Run: `cd android-CheckShot && ./gradlew :app:compileDebugKotlin` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add app/src/main/java/com/inspection/camera/ui/merge/MergeScreen.kt +git commit -m "fix: replace hardcoded colors with MaterialTheme.colorScheme in MergeScreen" +``` + +--- + +### Task 8: Replace hardcoded colors in SettingsScreen (remaining) + +**Files:** +- Modify: `app/src/main/java/com/inspection/camera/ui/settings/SettingsScreen.kt` + +- [ ] **Step 1: Replace remaining hardcoded colors** + +Save button text (line 117): +```kotlin +Text("保存", color = MaterialTheme.colorScheme.onPrimary) +``` + +TopAppBar colors (lines 120-124): +```kotlin +colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primary, + titleContentColor = MaterialTheme.colorScheme.onPrimary, + navigationIconContentColor = MaterialTheme.colorScheme.onPrimary +) +``` + +Description text colors (lines 160, 274, 324): +```kotlin +color = MaterialTheme.colorScheme.onSurfaceVariant +``` + +```kotlin +color = MaterialTheme.colorScheme.onSurfaceVariant +``` + +```kotlin +color = MaterialTheme.colorScheme.onSurfaceVariant +``` + +Section title color (line 358) — already changed in Task 5 to `MaterialTheme.colorScheme.primary`. + +- [ ] **Step 2: Verify compilation** + +Run: `cd android-CheckShot && ./gradlew :app:compileDebugKotlin` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add app/src/main/java/com/inspection/camera/ui/settings/SettingsScreen.kt +git commit -m "fix: replace hardcoded TopAppBar/card/description colors with MaterialTheme.colorScheme in SettingsScreen" +``` + +--- + +### Task 9: Verify CameraScreen overlay colors unchanged + +**Files:** +- Inspect: `app/src/main/java/com/inspection/camera/ui/camera/CameraScreen.kt` + +- [ ] **Step 1: Confirm camera overlay colors are preserved** + +CameraScreen uses semi-transparent overlays over the live camera preview: +- `Color.Black.copy(alpha = 0.3f)` for top/bottom control bars +- `Color.White` for FAB and icons +- `Color.Black.copy(alpha = 0.6f)` for location card +- `Color.Black.copy(alpha = 0.9f)` for flash effect + +These should remain as-is since they overlay live camera preview, not app background. + +- [ ] **Step 2: Optional — replace non-overlay colors if any are missed** + +The PermissionRequest composable uses no hardcoded colors that need changing (text with default style inherits from MaterialTheme). + +- [ ] **Step 3: Commit** + +```bash +git add app/src/main/java/com/inspection/camera/ui/camera/CameraScreen.kt +git commit -m "chore: confirm CameraScreen overlay colors are correct for dark mode" +``` + +--- + +### Verification: Full build + +- [ ] **Step 1: Full build** + +```bash +cd android-CheckShot && ./gradlew :app:assembleDebug +``` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 2: Remove unused imports across all modified files** + +Ensure no unused imports remain (e.g., `import com.inspection.camera.ui.theme.Primary` removed from GalleryScreen, MergeScreen). diff --git a/docs/superpowers/specs/2026-05-13-theme-mode-design.md b/docs/superpowers/specs/2026-05-13-theme-mode-design.md new file mode 100644 index 0000000..015b925 --- /dev/null +++ b/docs/superpowers/specs/2026-05-13-theme-mode-design.md @@ -0,0 +1,75 @@ +# 主题模式切换功能设计 + +## 概述 +为巡检相机 App 增加主题模式切换选项(跟随系统 / 浅色 / 暗色), +并修复当前暗色模式下字体颜色不可读的问题。 + +## 问题分析 +- Theme.kt 已定义 LightColorScheme 和 DarkColorScheme +- 所有界面使用硬编码 Color 值(Color.White, Color.Black, Color.LightGray, Primary 等) +- 未使用 MaterialTheme.colorScheme.* +- 暗色模式下主题切换为 DarkColorScheme,但 UI 颜色不变,导致文字不可见 + +## 方案:MaterialTheme.colorScheme 统一替换 +将所有硬编码颜色替换为 MaterialTheme.colorScheme 提供的语义化颜色。 + +### 新增类型 + +**ThemeMode 枚举**(data/models 包) +```kotlin +enum class ThemeMode { + FOLLOW_SYSTEM, + LIGHT, + DARK +} +``` + +### 修改文件 + +| 文件 | 改动 | +|------|------| +| `data/models/WatermarkModels.kt` | 新增 ThemeMode 枚举 | +| `data/PreferencesManager.kt` | 新增 KEY_THEME_MODE + themeMode Flow + setThemeMode | +| `ui/theme/Theme.kt` | InspectionCameraTheme 接受 themeMode 参数,取代 darkTheme | +| `ui/MainActivity.kt` | 读取 themeMode 偏好并传入 InspectionCameraTheme | +| `ui/settings/SettingsScreen.kt` | 新增"主题设置"区域,三个 RadioButton | +| `ui/camera/CameraScreen.kt` | 替换硬编码颜色 | +| `ui/gallery/GalleryScreen.kt` | 替换硬编码颜色 | +| `ui/merge/MergeScreen.kt` | 替换硬编码颜色 | + +### 颜色替换映射 + +| 原硬编码颜色 | 替换为 | 说明 | +|-------------|--------|------| +| `Primary` | `MaterialTheme.colorScheme.primary` | TopAppBar 背景、选中态、强调文字 | +| `Color.White` (TopAppBar文字) | `MaterialTheme.colorScheme.onPrimary` | TopAppBar 文字/图标 | +| `Color.White` (Card背景) | `MaterialTheme.colorScheme.surface` | Card 容器背景 | +| `Color.LightGray` (占位背景) | `MaterialTheme.colorScheme.surfaceVariant` | 网格占位区背景 | +| `Color.Gray` (辅助文字) | `MaterialTheme.colorScheme.onSurfaceVariant` | 描述性文字 | +| `Color.Black` (primary文字) | `MaterialTheme.colorScheme.onSurface` | 主要文字 | +| `Color.Red` (删除) | `MaterialTheme.colorScheme.error` | 删除/错误提示 | +| `Color.White` (按钮文字) | `MaterialTheme.colorScheme.onPrimary` | 按钮文字 | +| 相机覆盖层颜色 | 保持原样(覆盖在相机预览上,不属于主题背景) | 半透明黑/白色 | + +### 相机覆盖层的例外处理 +- 相机界面的半透明黑色底部控制栏(`Color.Black.copy(alpha = 0.3f)`)和白色 FAB 按钮**保持不变** +- 这些元素覆盖在相机实时预览之上,不属于 App 背景层,不应随主题变化 + +### 主题传递流程 + +``` +SettingsScreen → PreferencesManager.setThemeMode(mode) + ↓ +MainActivity ← PreferencesManager.themeMode (Flow) + ↓ +InspectionCameraTheme(themeMode) → 计算是否暗色 + ↓ +MaterialTheme(colorScheme = light/dark) + ↓ +所有界面使用 MaterialTheme.colorScheme.* 获取颜色 +``` + +### ThemeMode 逻辑 +- `FOLLOW_SYSTEM`: `isSystemInDarkTheme()` 决定 +- `LIGHT`: 强制用 LightColorScheme +- `DARK`: 强制用 DarkColorScheme