feat: 增加主题模式切换(跟随系统/浅色/暗色),修复暗色模式字体颜色不可读问题
This commit is contained in:
@@ -11,6 +11,7 @@ import androidx.datastore.preferences.preferencesDataStore
|
|||||||
import com.inspection.camera.data.models.ImageQuality
|
import com.inspection.camera.data.models.ImageQuality
|
||||||
import com.inspection.camera.data.models.LocationMode
|
import com.inspection.camera.data.models.LocationMode
|
||||||
import com.inspection.camera.data.models.MergeLayoutType
|
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.data.models.WatermarkStyle
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
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_MERGE_CONTENT = stringPreferencesKey("merge_content")
|
||||||
private val KEY_RECORDER_NAME = stringPreferencesKey("recorder_name")
|
private val KEY_RECORDER_NAME = stringPreferencesKey("recorder_name")
|
||||||
private val KEY_FILE_NAME_TEMPLATE = stringPreferencesKey("file_name_template")
|
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 ->
|
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}"
|
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) {
|
suspend fun setWatermarkStyle(style: WatermarkStyle) {
|
||||||
context.dataStore.edit { prefs ->
|
context.dataStore.edit { prefs ->
|
||||||
prefs[KEY_WATERMARK_STYLE] = style.name
|
prefs[KEY_WATERMARK_STYLE] = style.name
|
||||||
@@ -180,4 +191,10 @@ class PreferencesManager(private val context: Context) {
|
|||||||
prefs[KEY_FILE_NAME_TEMPLATE] = template
|
prefs[KEY_FILE_NAME_TEMPLATE] = template
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun setThemeMode(mode: ThemeMode) {
|
||||||
|
context.dataStore.edit { prefs ->
|
||||||
|
prefs[KEY_THEME_MODE] = mode.name
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,3 +64,9 @@ enum class MergeLayoutType(val rows: Int, val cols: Int, val displayName: String
|
|||||||
Grid2x2(2, 2, "2x2网格", 4),
|
Grid2x2(2, 2, "2x2网格", 4),
|
||||||
Grid3x3(3, 3, "3x3网格", 9)
|
Grid3x3(3, 3, "3x3网格", 9)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class ThemeMode {
|
||||||
|
FOLLOW_SYSTEM,
|
||||||
|
LIGHT,
|
||||||
|
DARK
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import androidx.compose.material3.NavigationBarItem
|
|||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -31,6 +32,7 @@ import androidx.navigation.compose.composable
|
|||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.inspection.camera.data.PreferencesManager
|
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.camera.CameraScreen
|
||||||
import com.inspection.camera.ui.gallery.GalleryScreen
|
import com.inspection.camera.ui.gallery.GalleryScreen
|
||||||
import com.inspection.camera.ui.merge.MergeScreen
|
import com.inspection.camera.ui.merge.MergeScreen
|
||||||
@@ -47,7 +49,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
preferencesManager = PreferencesManager(this)
|
preferencesManager = PreferencesManager(this)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
InspectionCameraTheme {
|
var themeMode by remember { mutableStateOf(ThemeMode.FOLLOW_SYSTEM) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
preferencesManager.themeMode.collect { themeMode = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
InspectionCameraTheme(themeMode = themeMode) {
|
||||||
MainApp(preferencesManager = preferencesManager)
|
MainApp(preferencesManager = preferencesManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ import androidx.lifecycle.LifecycleEventObserver
|
|||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.inspection.camera.ui.theme.Primary
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -130,10 +129,10 @@ fun GalleryScreen(onNavigateBack: () -> Unit) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = Primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
titleContentColor = Color.White,
|
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
navigationIconContentColor = Color.White,
|
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
actionIconContentColor = Color.White
|
actionIconContentColor = MaterialTheme.colorScheme.onPrimary
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -168,7 +167,7 @@ fun GalleryScreen(onNavigateBack: () -> Unit) {
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier.weight(1f).aspectRatio(1f)
|
modifier = Modifier.weight(1f).aspectRatio(1f)
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(RoundedCornerShape(8.dp))
|
||||||
.background(Color.LightGray)
|
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||||
.clickable {
|
.clickable {
|
||||||
selectedImageUri = img.uri
|
selectedImageUri = img.uri
|
||||||
}
|
}
|
||||||
@@ -236,7 +235,7 @@ fun GalleryScreen(onNavigateBack: () -> Unit) {
|
|||||||
Text("图片预览")
|
Text("图片预览")
|
||||||
Row {
|
Row {
|
||||||
IconButton(onClick = { showDeleteConfirm = true }) {
|
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 }) {
|
IconButton(onClick = { selectedImageUri = null }) {
|
||||||
Icon(Icons.Default.Close, contentDescription = "关闭")
|
Icon(Icons.Default.Close, contentDescription = "关闭")
|
||||||
@@ -271,7 +270,7 @@ fun GalleryScreen(onNavigateBack: () -> Unit) {
|
|||||||
showDeleteConfirm = false
|
showDeleteConfirm = false
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Text("确定删除", color = Color.Red)
|
Text("确定删除", color = MaterialTheme.colorScheme.error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
@@ -293,7 +292,7 @@ private fun ZoomableImage(imageUri: Uri, modifier: Modifier = Modifier) {
|
|||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(RoundedCornerShape(8.dp))
|
||||||
.background(Color.Black.copy(alpha = 0.05f))
|
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTransformGestures { _, pan, zoom, _ ->
|
detectTransformGestures { _, pan, zoom, _ ->
|
||||||
scale = (scale * zoom).coerceIn(1f, 5f)
|
scale = (scale * zoom).coerceIn(1f, 5f)
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ import com.inspection.camera.data.models.ImageItem
|
|||||||
import com.inspection.camera.data.models.ImageQuality
|
import com.inspection.camera.data.models.ImageQuality
|
||||||
import com.inspection.camera.data.models.MergeLayoutType
|
import com.inspection.camera.data.models.MergeLayoutType
|
||||||
import com.inspection.camera.data.models.WatermarkStyle
|
import com.inspection.camera.data.models.WatermarkStyle
|
||||||
import com.inspection.camera.ui.theme.Primary
|
|
||||||
import com.inspection.camera.util.ImageProcessor
|
import com.inspection.camera.util.ImageProcessor
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -292,9 +291,9 @@ fun MergeScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = Primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
titleContentColor = Color.White,
|
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
navigationIconContentColor = Color.White
|
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -341,7 +340,7 @@ fun MergeScreen(
|
|||||||
Text(
|
Text(
|
||||||
text = autoImportText,
|
text = autoImportText,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = Primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||||
@@ -365,13 +364,13 @@ fun MergeScreen(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(4.dp))
|
.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 }
|
.clickable { imageQuality = quality }
|
||||||
.padding(horizontal = 12.dp, vertical = 4.dp)
|
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = quality.displayName,
|
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
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -403,7 +402,7 @@ fun MergeScreen(
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.aspectRatio(1f)
|
.aspectRatio(1f)
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(RoundedCornerShape(8.dp))
|
||||||
.background(Color.LightGray)
|
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||||
.then(
|
.then(
|
||||||
if (index < images.size) {
|
if (index < images.size) {
|
||||||
Modifier.clickable {
|
Modifier.clickable {
|
||||||
@@ -435,12 +434,12 @@ fun MergeScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopEnd)
|
.align(Alignment.TopEnd)
|
||||||
.size(32.dp)
|
.size(32.dp)
|
||||||
.background(Color.Black.copy(alpha = 0.5f), CircleShape)
|
.background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.5f), CircleShape)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Close,
|
Icons.Default.Close,
|
||||||
contentDescription = "删除",
|
contentDescription = "删除",
|
||||||
tint = Color.White,
|
tint = MaterialTheme.colorScheme.onPrimary,
|
||||||
modifier = Modifier.size(16.dp)
|
modifier = Modifier.size(16.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -448,7 +447,7 @@ fun MergeScreen(
|
|||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Add,
|
Icons.Default.Add,
|
||||||
contentDescription = "添加图片",
|
contentDescription = "添加图片",
|
||||||
tint = Color.Gray,
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.size(48.dp)
|
modifier = Modifier.size(48.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -633,20 +632,20 @@ private fun LayoutOption(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(RoundedCornerShape(8.dp))
|
||||||
.background(if (isSelected) Primary else Color.LightGray)
|
.background(if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant)
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.padding(12.dp)
|
.padding(12.dp)
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(
|
Text(
|
||||||
text = displayText,
|
text = displayText,
|
||||||
color = if (isSelected) Color.White else Color.Black
|
color = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Check,
|
Icons.Default.Check,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = Color.White,
|
tint = MaterialTheme.colorScheme.onPrimary,
|
||||||
modifier = Modifier.size(16.dp)
|
modifier = Modifier.size(16.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,14 +35,13 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.inspection.camera.data.PreferencesManager
|
import com.inspection.camera.data.PreferencesManager
|
||||||
import com.inspection.camera.data.models.ImageQuality
|
import com.inspection.camera.data.models.ImageQuality
|
||||||
import com.inspection.camera.data.models.LocationMode
|
import com.inspection.camera.data.models.LocationMode
|
||||||
import com.inspection.camera.data.models.MergeLayoutType
|
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.data.models.WatermarkStyle
|
||||||
import com.inspection.camera.ui.theme.Primary
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -61,6 +60,7 @@ fun SettingsScreen(
|
|||||||
var defaultTheme by remember { mutableStateOf("") }
|
var defaultTheme by remember { mutableStateOf("") }
|
||||||
var recorderName by remember { mutableStateOf("") }
|
var recorderName by remember { mutableStateOf("") }
|
||||||
var fileNameTemplate by remember { mutableStateOf("{project}_{inspector}_{date}") }
|
var fileNameTemplate by remember { mutableStateOf("{project}_{inspector}_{date}") }
|
||||||
|
var themeMode by remember { mutableStateOf(ThemeMode.FOLLOW_SYSTEM) }
|
||||||
|
|
||||||
// 加载配置
|
// 加载配置
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -87,6 +87,9 @@ scope.launch {
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
preferencesManager.fileNameTemplate.collect { fileNameTemplate = it }
|
preferencesManager.fileNameTemplate.collect { fileNameTemplate = it }
|
||||||
}
|
}
|
||||||
|
scope.launch {
|
||||||
|
preferencesManager.themeMode.collect { themeMode = it }
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -114,13 +117,13 @@ scope.launch {
|
|||||||
// 返回主界面
|
// 返回主界面
|
||||||
onNavigateBack()
|
onNavigateBack()
|
||||||
}) {
|
}) {
|
||||||
Text("保存", color = Color.White)
|
Text("保存", color = MaterialTheme.colorScheme.onPrimary)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = Primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
titleContentColor = Color.White,
|
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
navigationIconContentColor = Color.White
|
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -157,7 +160,7 @@ scope.launch {
|
|||||||
else -> ""
|
else -> ""
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = Color.Gray,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(start = 40.dp)
|
modifier = Modifier.padding(start = 40.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -271,7 +274,7 @@ scope.launch {
|
|||||||
else -> ""
|
else -> ""
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = Color.Gray,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(start = 40.dp)
|
modifier = Modifier.padding(start = 40.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -281,6 +284,34 @@ scope.launch {
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
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 = "通用设置") {
|
SettingsSection(title = "通用设置") {
|
||||||
SettingsItem(title = "默认拼图标题") {
|
SettingsItem(title = "默认拼图标题") {
|
||||||
@@ -321,7 +352,7 @@ SettingsItem(title = "记录人信息") {
|
|||||||
Text(
|
Text(
|
||||||
text = "可用占位符:{project} {device} {inspector} {date} {time}",
|
text = "可用占位符:{project} {device} {inspector} {date} {time}",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = Color.Gray
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,13 +380,13 @@ private fun SettingsSection(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
colors = CardDefaults.cardColors(containerColor = Color.White)
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = Primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
content()
|
content()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import androidx.compose.runtime.SideEffect
|
|||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
import com.inspection.camera.data.models.ThemeMode
|
||||||
|
|
||||||
private val LightColorScheme = lightColorScheme(
|
private val LightColorScheme = lightColorScheme(
|
||||||
primary = Primary,
|
primary = Primary,
|
||||||
@@ -37,10 +38,15 @@ private val DarkColorScheme = darkColorScheme(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun InspectionCameraTheme(
|
fun InspectionCameraTheme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
themeMode: ThemeMode = ThemeMode.FOLLOW_SYSTEM,
|
||||||
content: @Composable () -> Unit
|
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
|
val view = LocalView.current
|
||||||
|
|
||||||
if (!view.isInEditMode) {
|
if (!view.isInEditMode) {
|
||||||
|
|||||||
519
docs/superpowers/plans/2026-05-13-theme-mode-plan.md
Normal file
519
docs/superpowers/plans/2026-05-13-theme-mode-plan.md
Normal 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).
|
||||||
75
docs/superpowers/specs/2026-05-13-theme-mode-design.md
Normal file
75
docs/superpowers/specs/2026-05-13-theme-mode-design.md
Normal 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
|
||||||
Reference in New Issue
Block a user