* feat(#154): add word-by-word typing effect toggle and speed slider in settings * fix(settings): import Slider to fix build failure --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
8c9c11df23
commit
3c5fcf504c
4 files changed
+167
-6
No files matched your search
@@ -42,6 +42,7 @@ import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.Text
|
||||
@@ -236,6 +237,75 @@ fun SettingsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// Chat section
|
||||
SectionCard(title = "Chat") {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = "Typing Effect",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
Text(
|
||||
text = "Animate new messages word by word",
|
||||
style =
|
||||
MaterialTheme.typography.bodySmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = state.typingEffectEnabled,
|
||||
onCheckedChange = viewModel::onTypingEffectEnabledChange,
|
||||
colors =
|
||||
SwitchDefaults.colors(
|
||||
checkedThumbColor = MaterialTheme.colorScheme.primary,
|
||||
checkedTrackColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Delay slider — only visible when effect is enabled
|
||||
AnimatedVisibility(visible = state.typingEffectEnabled) {
|
||||
Column(modifier = Modifier.padding(top = 12.dp)) {
|
||||
Text(
|
||||
text = "Delay: ${state.typingEffectDelayMs} ms",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Slider(
|
||||
value = state.typingEffectDelayMs.toFloat(),
|
||||
onValueChange = { viewModel.onTypingEffectDelayMsChange(it.toInt()) },
|
||||
valueRange = 10f..100f,
|
||||
steps = 8, // 10, 20, 30, 40, 50, 60, 70, 80, 90, 100
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text = "10 ms",
|
||||
style =
|
||||
MaterialTheme.typography.labelSmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
Text(
|
||||
text = "100 ms",
|
||||
style =
|
||||
MaterialTheme.typography.labelSmall.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation Bar section
|
||||
SectionCard(title = "Navigation Bar") {
|
||||
Text(
|
||||
|
||||
@@ -25,6 +25,8 @@ data class SettingsUiState(
|
||||
val testResult: String? = null,
|
||||
val isSaved: Boolean = false,
|
||||
val selectedNavItems: List<String> = emptyList(),
|
||||
val typingEffectEnabled: Boolean = false,
|
||||
val typingEffectDelayMs: Int = 30,
|
||||
)
|
||||
|
||||
class SettingsViewModel : ViewModel() {
|
||||
@@ -47,6 +49,8 @@ class SettingsViewModel : ViewModel() {
|
||||
// was missing — always defaulted to ThemePreference.SYSTEM.
|
||||
themePreference = AuthManager.getThemePreference(),
|
||||
selectedNavItems = AuthManager.getBottomNavItems(),
|
||||
typingEffectEnabled = AuthManager.isTypingEffectEnabled(),
|
||||
typingEffectDelayMs = AuthManager.getTypingEffectDelayMs(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -71,6 +75,14 @@ class SettingsViewModel : ViewModel() {
|
||||
_uiState.update { it.copy(themePreference = theme, isSaved = false) }
|
||||
}
|
||||
|
||||
fun onTypingEffectEnabledChange(enabled: Boolean) {
|
||||
_uiState.update { it.copy(typingEffectEnabled = enabled, isSaved = false) }
|
||||
}
|
||||
|
||||
fun onTypingEffectDelayMsChange(delayMs: Int) {
|
||||
_uiState.update { it.copy(typingEffectDelayMs = delayMs, isSaved = false) }
|
||||
}
|
||||
|
||||
/** All screens available for bottom-nav selection (name → display label). */
|
||||
val availableNavItems: List<Pair<String, String>> =
|
||||
listOf(
|
||||
@@ -128,6 +140,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.setTypingEffectEnabled(state.typingEffectEnabled)
|
||||
AuthManager.setTypingEffectDelayMs(state.typingEffectDelayMs)
|
||||
ApiClient.rebuild()
|
||||
|
||||
_uiState.update { it.copy(isSaved = true, testResult = null) }
|
||||
|
||||
Reference in new issue
Block a user