feat: 增加主题模式切换(跟随系统/浅色/暗色),修复暗色模式字体颜色不可读问题

This commit is contained in:
Developer
2026-05-13 20:54:50 +08:00
parent acddb7d53e
commit 62294598ca
9 changed files with 704 additions and 44 deletions

View File

@@ -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<WatermarkStyle> = 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<ThemeMode> = 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
}
}
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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
color = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface
)
if (isSelected) {
Icon(
Icons.Default.Check,
contentDescription = null,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
modifier = Modifier.size(16.dp)
)
}

View File

@@ -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()

View File

@@ -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) {

View File

@@ -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<ThemeMode> = 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).

View File

@@ -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