feat(settings): in-app chat font size setting (#1004) (#1005)

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-08-29 11:36:56 +03:00
1 parent db3f8c736e
commit 5bc6e5f855
14 files changed
+461 -182

No files matched your search

@@ -191,6 +191,8 @@ internal fun SettingsChatPage(
onTypingEffectEnabledChange = viewModel::onTypingEffectEnabledChange,
typingEffectDelayMs = state.typingEffectDelayMs,
onTypingEffectDelayMsChange = viewModel::onTypingEffectDelayMsChange,
chatFontScale = state.chatFontScale,
onChatFontScaleChange = viewModel::onChatFontScaleChange,
)
}
}
@@ -36,6 +36,7 @@ data class SettingsUiState(
val isSaved: Boolean = false,
val typingEffectEnabled: Boolean = false,
val typingEffectDelayMs: Int = 30,
val chatFontScale: Float = 1.0f,
val profiles: List<ConnectionProfile> = emptyList(),
val selectedProfileId: String? = null,
val renameProfileName: String = "",
@@ -76,6 +77,7 @@ class SettingsViewModel(
val themePreset = AuthManager.getThemePreset()
val typingEffectEnabled = AuthManager.isTypingEffectEnabled()
val typingEffectDelayMs = AuthManager.getTypingEffectDelayMs()
val chatFontScale = AuthManager.getChatFontScale()
val profiles = AuthManager.getConnectionProfiles()
val appLanguage = AuthManager.getAppLanguage()
val renameProfileName =
@@ -95,6 +97,7 @@ class SettingsViewModel(
themePreset = themePreset,
typingEffectEnabled = typingEffectEnabled,
typingEffectDelayMs = typingEffectDelayMs,
chatFontScale = chatFontScale,
profiles = profiles,
selectedProfileId = selectedId,
renameProfileName = renameProfileName,
@@ -310,6 +313,11 @@ class SettingsViewModel(
AuthManager.setTypingEffectDelayMs(delayMs)
}
fun onChatFontScaleChange(scale: Float) {
_uiState.update { it.copy(chatFontScale = scale, isSaved = false) }
AuthManager.setChatFontScale(scale)
}
/** Clear all auth credentials — logs out and returns to landing screen. */
fun logout() {
AuthManager.setToken(null)
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.MaterialTheme
@@ -18,22 +19,29 @@ import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Slider
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import com.m57.hermescontrol.R
import com.m57.hermescontrol.theme.ThemePreference
import com.m57.hermescontrol.theme.ThemePreset
import com.m57.hermescontrol.ui.settings.SectionCard
import kotlin.math.abs
import kotlin.math.roundToInt
@Composable
internal fun AppearanceSection(
@@ -194,10 +202,172 @@ internal fun ChatSection(
onTypingEffectEnabledChange: (Boolean) -> Unit,
typingEffectDelayMs: Int,
onTypingEffectDelayMsChange: (Int) -> Unit,
chatFontScale: Float = 1.0f,
onChatFontScaleChange: (Float) -> Unit = {},
) {
val fontScaleOptions = listOf(0.85f, 1.0f, 1.15f, 1.30f, 1.50f)
val currentIndex =
fontScaleOptions.indexOfFirst { abs(it - chatFontScale) < 0.05f }
.takeIf { it >= 0 } ?: 1
SectionCard {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = stringResource(R.string.settings_item_chat_font_size),
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = stringResource(R.string.settings_desc_chat_font_size),
style =
MaterialTheme.typography.bodySmall.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant,
),
)
}
val scaleLabel =
when (currentIndex) {
0 -> stringResource(R.string.settings_font_scale_small)
1 -> stringResource(R.string.settings_font_scale_default)
2 -> stringResource(R.string.settings_font_scale_large)
3 -> stringResource(R.string.settings_font_scale_xlarge)
else -> stringResource(R.string.settings_font_scale_huge)
}
Text(
text = scaleLabel,
style =
MaterialTheme.typography.labelMedium.copy(
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.SemiBold,
),
)
}
Spacer(modifier = Modifier.height(8.dp))
Slider(
value = currentIndex.toFloat(),
onValueChange = { onChatFontScaleChange(fontScaleOptions[it.roundToInt()]) },
valueRange = 0f..(fontScaleOptions.size - 1).toFloat(),
steps = fontScaleOptions.size - 2,
modifier = Modifier.fillMaxWidth(),
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = "0.85×",
style =
MaterialTheme.typography.labelSmall.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant,
),
)
Text(
text = "1.0×",
style =
MaterialTheme.typography.labelSmall.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant,
),
)
Text(
text = "1.5×",
style =
MaterialTheme.typography.labelSmall.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant,
),
)
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(R.string.settings_chat_preview_title),
style =
MaterialTheme.typography.labelMedium.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant,
),
)
Spacer(modifier = Modifier.height(8.dp))
val currentDensity = LocalDensity.current
val previewDensity =
remember(currentDensity, chatFontScale) {
Density(
density = currentDensity.density,
fontScale = currentDensity.fontScale * chatFontScale,
)
}
CompositionLocalProvider(LocalDensity provides previewDensity) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colorScheme.surfaceContainer,
) {
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
// User bubble preview
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
Surface(
shape =
RoundedCornerShape(
topStart = 14.dp,
topEnd = 14.dp,
bottomStart = 14.dp,
bottomEnd = 4.dp,
),
color = MaterialTheme.colorScheme.primary,
) {
Text(
text = stringResource(R.string.settings_chat_preview_user),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onPrimary,
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp),
)
}
}
// Assistant bubble preview
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Start,
) {
Surface(
shape =
RoundedCornerShape(
topStart = 14.dp,
topEnd = 14.dp,
bottomStart = 4.dp,
bottomEnd = 14.dp,
),
color = MaterialTheme.colorScheme.surfaceContainerHigh,
) {
Text(
text = stringResource(R.string.settings_chat_preview_agent),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp),
)
}
}
}
}
}
Spacer(modifier = Modifier.height(20.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,