feat(#154): add word-by-word typing effect toggle and speed slider in settings (#165)

* 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:
M57andM57 authored and GitHub committed 2026-06-20 17:09:42 +03:00
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) }