From 6785578b6df3cd486ff7baaa2c2c9599f639c0fa Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Fri, 23 Jan 2026 11:30:52 +0100 Subject: [PATCH 1/6] Make dots question marks etc take a bit more time than other chars --- .../feature/claim/chat/ui/ClaimChatDestination.kt | 14 ++++++++++---- .../feature/claim/chat/ui/ClaimChatUiComponents.kt | 3 +-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt index 258f4c3478..02288315e6 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt @@ -134,6 +134,7 @@ import hedvig.resources.general_continue_button import hedvig.resources.general_error import hedvig.resources.something_went_wrong import kotlin.time.Clock +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.jetbrains.compose.resources.stringResource @@ -754,17 +755,22 @@ private fun AnimatedRevealText( visibleState: MutableTransitionState, modifier: Modifier = Modifier, style: TextStyle = LocalTextStyle.current, - delayPerChar: Int = 30, - charAnimDuration: Int = 150, onAnimationFinished: () -> Unit = {}, ) { + val charAnimDuration: Int = 150 var visibleChars by remember { mutableStateOf(0) } LaunchedEffect(visibleState.targetState, text) { if (visibleState.targetState) { visibleChars = 0 - repeat(text.length) { index -> - delay(delayPerChar.toLong()) + text.toCharArray().forEachIndexed { index, char -> + delay( + if (char in listOf('.', '?', '!', '\n', '\t')) { + 200.milliseconds + } else { + 20.milliseconds + }, + ) visibleChars = index + 1 } delay(charAnimDuration.toLong()) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt index e26ed022e0..f644ad21ac 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt @@ -697,12 +697,11 @@ internal fun DateSelectBubble( modifier: Modifier = Modifier, errorText: String? = null, ) { - Column { + Column(modifier) { DatePickerWithDialog( datePickerState, canInteract = true, startText = questionLabel ?: "", - modifier = modifier, ) AnimatedVisibility( errorText != null && From 3203cf117519490252ee7ecc07c751ed16b9fa7a Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Fri, 23 Jan 2026 11:52:00 +0100 Subject: [PATCH 2/6] Use linear interpolation to speed up the text reveal animation on long texts --- .../claim/chat/ui/ClaimChatDestination.kt | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt index 02288315e6..6b3f32d6e3 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt @@ -760,15 +760,41 @@ private fun AnimatedRevealText( val charAnimDuration: Int = 150 var visibleChars by remember { mutableStateOf(0) } + val (regularCharDelay, specialCharDelay) = remember(text) { + val textLength = text.length + val baseRegularDelayMillis = 20 + val baseSpecialDelayMillis = 200 + + // Calculate speed multiplier based on text length + val range = 0..400 + val minSpeedAt = 50 + val minSpeedMultiplier = 0.2 + val maxSpeedMultiplier = 1.0 + val speedMultiplier = when { + textLength <= minSpeedAt -> maxSpeedMultiplier + textLength >= minSpeedAt + range.last -> minSpeedMultiplier + else -> { + // Gradually speed up between 50 and 450 characters with a linear interpolation from 1.0 to 0.2 + val numberOfCharactersInRange = textLength - minSpeedAt + maxSpeedMultiplier - (numberOfCharactersInRange / range.last) * (maxSpeedMultiplier - minSpeedMultiplier) + } + }.coerceIn(minSpeedMultiplier, maxSpeedMultiplier) + + val regularDelay = (baseRegularDelayMillis * speedMultiplier).toInt().coerceAtLeast(baseRegularDelayMillis / 5) + val specialDelay = (baseSpecialDelayMillis * speedMultiplier).toInt().coerceAtLeast(baseSpecialDelayMillis / 5) + + regularDelay to specialDelay + } + LaunchedEffect(visibleState.targetState, text) { if (visibleState.targetState) { visibleChars = 0 text.toCharArray().forEachIndexed { index, char -> delay( if (char in listOf('.', '?', '!', '\n', '\t')) { - 200.milliseconds + specialCharDelay.milliseconds } else { - 20.milliseconds + regularCharDelay.milliseconds }, ) visibleChars = index + 1 From 9d2e9a4169ee87857d4979832625ce6f0a6e3bb9 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Fri, 23 Jan 2026 11:59:36 +0100 Subject: [PATCH 3/6] Fix delay coming before punctuations rather than after --- .../com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt index 6b3f32d6e3..ea6448ec91 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt @@ -790,6 +790,7 @@ private fun AnimatedRevealText( if (visibleState.targetState) { visibleChars = 0 text.toCharArray().forEachIndexed { index, char -> + visibleChars = index + 1 delay( if (char in listOf('.', '?', '!', '\n', '\t')) { specialCharDelay.milliseconds @@ -797,7 +798,6 @@ private fun AnimatedRevealText( regularCharDelay.milliseconds }, ) - visibleChars = index + 1 } delay(charAnimDuration.toLong()) onAnimationFinished() From c8bf5fb86d5e974399cd661d12ba44f264e256dc Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Fri, 23 Jan 2026 11:59:43 +0100 Subject: [PATCH 4/6] Fix modifiers being used wrongly --- .../claim/chat/ui/ClaimChatUiComponents.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt index f644ad21ac..8ab6a68e29 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt @@ -149,7 +149,7 @@ internal fun ContentSelectChips( StepContent.ContentSelectStyle.BINARY -> { Row( - Modifier.fillMaxWidth(), + modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly, ) { for (item in options) { @@ -287,9 +287,9 @@ internal fun YesNoBubble( stringResource(Res.string.GENERAL_NO), ), ) - Column { + Column(modifier) { Row( - modifier = modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.End, ) { @@ -312,7 +312,7 @@ internal fun YesNoBubble( if (errorText != null) { Spacer(Modifier.height(4.dp)) Row( - modifier = modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.End, ) { @@ -349,14 +349,14 @@ internal fun SingleSelectBubbleWithDialog( }, ) } - Column { + Column(modifier) { HedvigBigCard( onClick = { showDialog = true }, labelText = questionLabel, inputText = options.firstOrNull { it.id == selectedOptionId }?.text, - modifier = modifier, + modifier = Modifier.fillMaxWidth(), enabled = true, ) AnimatedVisibility(errorText != null) { @@ -395,7 +395,7 @@ internal fun MultiSelectBubbleWithDialog( buttonText = stringResource(Res.string.general_save_button), ) } - Column { + Column(modifier) { HedvigBigCard( onClick = { showDialog = true }, labelText = questionLabel, @@ -404,7 +404,7 @@ internal fun MultiSelectBubbleWithDialog( else -> options.filter { it.id in selectedOptionIds } .joinToString(transform = RadioOption::text) }, - modifier = modifier, + modifier = Modifier.fillMaxWidth(), enabled = true, ) AnimatedVisibility(errorText != null) { @@ -702,6 +702,7 @@ internal fun DateSelectBubble( datePickerState, canInteract = true, startText = questionLabel ?: "", + Modifier.fillMaxWidth() ) AnimatedVisibility( errorText != null && @@ -817,7 +818,7 @@ internal fun ChatClaimSummaryTopContent( ) Spacer(Modifier.height(8.dp)) CompositionLocalProvider(LocalContentColor provides HedvigTheme.colorScheme.textSecondary) { - Column(modifier) { + Column(Modifier) { for (displayItem in displayItems) { HorizontalItemsWithMaximumSpaceTaken( spaceBetween = 8.dp, From 0cba0278a62f489b597d581a4ed8a03bf58ae35e Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Fri, 23 Jan 2026 12:38:35 +0100 Subject: [PATCH 5/6] Make text reveal faster for long texts --- .../claim/chat/ui/ClaimChatDestination.kt | 74 +++++++++++-------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt index ea6448ec91..52648e2c7f 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt @@ -134,6 +134,7 @@ import hedvig.resources.general_continue_button import hedvig.resources.general_error import hedvig.resources.something_went_wrong import kotlin.time.Clock +import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -760,44 +761,19 @@ private fun AnimatedRevealText( val charAnimDuration: Int = 150 var visibleChars by remember { mutableStateOf(0) } - val (regularCharDelay, specialCharDelay) = remember(text) { - val textLength = text.length - val baseRegularDelayMillis = 20 - val baseSpecialDelayMillis = 200 - - // Calculate speed multiplier based on text length - val range = 0..400 - val minSpeedAt = 50 - val minSpeedMultiplier = 0.2 - val maxSpeedMultiplier = 1.0 - val speedMultiplier = when { - textLength <= minSpeedAt -> maxSpeedMultiplier - textLength >= minSpeedAt + range.last -> minSpeedMultiplier - else -> { - // Gradually speed up between 50 and 450 characters with a linear interpolation from 1.0 to 0.2 - val numberOfCharactersInRange = textLength - minSpeedAt - maxSpeedMultiplier - (numberOfCharactersInRange / range.last) * (maxSpeedMultiplier - minSpeedMultiplier) - } - }.coerceIn(minSpeedMultiplier, maxSpeedMultiplier) - - val regularDelay = (baseRegularDelayMillis * speedMultiplier).toInt().coerceAtLeast(baseRegularDelayMillis / 5) - val specialDelay = (baseSpecialDelayMillis * speedMultiplier).toInt().coerceAtLeast(baseSpecialDelayMillis / 5) - - regularDelay to specialDelay - } + val charDelay = calculateCharDelay(text) LaunchedEffect(visibleState.targetState, text) { if (visibleState.targetState) { visibleChars = 0 text.toCharArray().forEachIndexed { index, char -> visibleChars = index + 1 - delay( - if (char in listOf('.', '?', '!', '\n', '\t')) { - specialCharDelay.milliseconds - } else { - regularCharDelay.milliseconds - }, - ) + val specialCharDelayMultiplier = when (char) { + ',' -> 5 + in listOf('.', '?', '!', '\n', '\t') -> 10 + else -> 1 + } + delay(charDelay * specialCharDelayMultiplier) } delay(charAnimDuration.toLong()) onAnimationFinished() @@ -832,6 +808,40 @@ private fun AnimatedRevealText( ) } +/** + * Speed multiplier decreases for longer text to avoid tedious animations + * Short text (≤50 chars): full speed (1.0x multiplier) + * Medium text (50-450 chars): linear interpolation + * Long text (≥450 chars): 5x faster (0.2x multiplier) + */ +@Composable +private fun calculateCharDelay(text: String): Duration = remember(text) { + val textLength = text.length + val baseRegularDelayMillis = 20 + + val shortTextThreshold = 50 + val longTextThreshold = 350 + val slowestMultiplier = 1.0 + val fastestMultiplier = 0.2 + + val speedMultiplier = when { + textLength <= shortTextThreshold -> slowestMultiplier + textLength >= longTextThreshold -> fastestMultiplier + else -> { + val characterRange = longTextThreshold - shortTextThreshold + val charactersAboveThreshold = textLength - shortTextThreshold + val interpolationProgress = charactersAboveThreshold.toDouble() / characterRange + slowestMultiplier - interpolationProgress * (slowestMultiplier - fastestMultiplier) + } + }.coerceIn(fastestMultiplier, slowestMultiplier) + + // Apply speed multiplier but ensure delays don't go below 20% of base (to remain readable) + val minimumDelayRatio = 0.2 + (baseRegularDelayMillis * speedMultiplier) + .coerceAtLeast((baseRegularDelayMillis * minimumDelayRatio)) + .milliseconds +} + @Composable private fun StepBottomContent( stepItem: ClaimIntentStep, From 2a3bc0c21b0b3b13e8bad83e2d5a16476150ebd8 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Fri, 23 Jan 2026 12:40:18 +0100 Subject: [PATCH 6/6] Add fallback for extremely long responses (unlikely to exist) --- .../hedvig/feature/claim/chat/ui/ClaimChatDestination.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt index 52648e2c7f..4cf1fc2b23 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt @@ -824,8 +824,13 @@ private fun calculateCharDelay(text: String): Duration = remember(text) { val slowestMultiplier = 1.0 val fastestMultiplier = 0.2 + // Extreme fallback for extreme cases + val superLongThreshold = 800 + val superFastestMultiplier = 0.05 + val speedMultiplier = when { textLength <= shortTextThreshold -> slowestMultiplier + textLength >= superLongThreshold -> superFastestMultiplier textLength >= longTextThreshold -> fastestMultiplier else -> { val characterRange = longTextThreshold - shortTextThreshold @@ -835,7 +840,6 @@ private fun calculateCharDelay(text: String): Duration = remember(text) { } }.coerceIn(fastestMultiplier, slowestMultiplier) - // Apply speed multiplier but ensure delays don't go below 20% of base (to remain readable) val minimumDelayRatio = 0.2 (baseRegularDelayMillis * speedMultiplier) .coerceAtLeast((baseRegularDelayMillis * minimumDelayRatio))