feat(#208): add Monochrome, Gruvbox, Catppuccin and AMOLED theme presets
This commit is contained in:
1 parent
f738f33fc5
commit
8e8fca756a
6 files changed
+368
-36
No files matched your search
@@ -64,6 +64,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.m57.hermescontrol.BuildConfig
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.theme.ThemePreference
|
||||
import com.m57.hermescontrol.theme.ThemePreset
|
||||
import com.m57.hermescontrol.ui.common.HermesScaffold
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -322,6 +323,88 @@ fun SettingsScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_item_use_dynamic_colors),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.settings_desc_use_dynamic_colors),
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = state.useDynamicColors,
|
||||
onCheckedChange = viewModel::onUseDynamicColorsChange,
|
||||
colors =
|
||||
SwitchDefaults.colors(
|
||||
checkedThumbColor = MaterialTheme.colorScheme.primary,
|
||||
checkedTrackColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.settings_item_theme_preset),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
var presetsExpanded by remember { mutableStateOf(false) }
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
OutlinedButton(
|
||||
onClick = { presetsExpanded = true },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
when (state.themePreset) {
|
||||
ThemePreset.DEFAULT -> stringResource(R.string.theme_preset_default)
|
||||
ThemePreset.MONOCHROME -> stringResource(R.string.theme_preset_monochrome)
|
||||
ThemePreset.GRUVBOX -> stringResource(R.string.theme_preset_gruvbox)
|
||||
ThemePreset.CATPPUCCIN -> stringResource(R.string.theme_preset_catppuccin)
|
||||
ThemePreset.AMOLED -> stringResource(R.string.theme_preset_amoled)
|
||||
},
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = presetsExpanded,
|
||||
onDismissRequest = { presetsExpanded = false },
|
||||
modifier = Modifier.fillMaxWidth(0.85f),
|
||||
) {
|
||||
ThemePreset.entries.forEach { preset ->
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(
|
||||
when (preset) {
|
||||
ThemePreset.DEFAULT -> stringResource(R.string.theme_preset_default)
|
||||
ThemePreset.MONOCHROME -> stringResource(R.string.theme_preset_monochrome)
|
||||
ThemePreset.GRUVBOX -> stringResource(R.string.theme_preset_gruvbox)
|
||||
ThemePreset.CATPPUCCIN -> stringResource(R.string.theme_preset_catppuccin)
|
||||
ThemePreset.AMOLED -> stringResource(R.string.theme_preset_amoled)
|
||||
},
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.onThemePresetChange(preset)
|
||||
presetsExpanded = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chat section
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.m57.hermescontrol.data.remote.ApiClient
|
||||
import com.m57.hermescontrol.data.remote.NetworkResult
|
||||
import com.m57.hermescontrol.data.remote.safeApiCall
|
||||
import com.m57.hermescontrol.theme.ThemePreference
|
||||
import com.m57.hermescontrol.theme.ThemePreset
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -22,6 +23,8 @@ data class SettingsUiState(
|
||||
val token: String = "",
|
||||
val autoReconnect: Boolean = true,
|
||||
val themePreference: ThemePreference = ThemePreference.SYSTEM,
|
||||
val useDynamicColors: Boolean = true,
|
||||
val themePreset: ThemePreset = ThemePreset.DEFAULT,
|
||||
val isTesting: Boolean = false,
|
||||
val testResult: String? = null,
|
||||
val isSaved: Boolean = false,
|
||||
@@ -53,6 +56,8 @@ class SettingsViewModel : ViewModel() {
|
||||
// choice from persistent storage on init. Previously this slot
|
||||
// was missing — always defaulted to ThemePreference.SYSTEM.
|
||||
themePreference = AuthManager.getThemePreference(),
|
||||
useDynamicColors = AuthManager.isUseDynamicColors(),
|
||||
themePreset = AuthManager.getThemePreset(),
|
||||
selectedNavItems = AuthManager.getBottomNavItems(),
|
||||
typingEffectEnabled = AuthManager.isTypingEffectEnabled(),
|
||||
typingEffectDelayMs = AuthManager.getTypingEffectDelayMs(),
|
||||
@@ -122,6 +127,16 @@ class SettingsViewModel : ViewModel() {
|
||||
AuthManager.setThemePreference(theme)
|
||||
}
|
||||
|
||||
fun onUseDynamicColorsChange(enabled: Boolean) {
|
||||
_uiState.update { it.copy(useDynamicColors = enabled, isSaved = false) }
|
||||
AuthManager.setUseDynamicColors(enabled)
|
||||
}
|
||||
|
||||
fun onThemePresetChange(preset: ThemePreset) {
|
||||
_uiState.update { it.copy(themePreset = preset, isSaved = false) }
|
||||
AuthManager.setThemePreset(preset)
|
||||
}
|
||||
|
||||
fun onTypingEffectEnabledChange(enabled: Boolean) {
|
||||
_uiState.update { it.copy(typingEffectEnabled = enabled, isSaved = false) }
|
||||
}
|
||||
@@ -188,6 +203,8 @@ class SettingsViewModel : ViewModel() {
|
||||
// B6 (Jun 18 2026, kanban t_86e9be9b): persist theme choice so it
|
||||
// survives a cold start (was previously dropped on save()).
|
||||
AuthManager.setThemePreference(state.themePreference)
|
||||
AuthManager.setUseDynamicColors(state.useDynamicColors)
|
||||
AuthManager.setThemePreset(state.themePreset)
|
||||
AuthManager.setTypingEffectEnabled(state.typingEffectEnabled)
|
||||
AuthManager.setTypingEffectDelayMs(state.typingEffectDelayMs)
|
||||
ApiClient.rebuild()
|
||||
|
||||
Reference in new issue
Block a user