Skip to content

Commit 607f761

Browse files
Merge pull request #2816 from HedvigInsurance/feature/faster-text-reaveal
Feature: faster text reveal (GEN-4775)
2 parents 04b18da + 2a3bc0c commit 607f761

2 files changed

Lines changed: 61 additions & 15 deletions

File tree

app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ import hedvig.resources.general_continue_button
134134
import hedvig.resources.general_error
135135
import hedvig.resources.something_went_wrong
136136
import kotlin.time.Clock
137+
import kotlin.time.Duration
138+
import kotlin.time.Duration.Companion.milliseconds
137139
import kotlinx.coroutines.delay
138140
import kotlinx.coroutines.launch
139141
import org.jetbrains.compose.resources.stringResource
@@ -754,18 +756,24 @@ private fun AnimatedRevealText(
754756
visibleState: MutableTransitionState<Boolean>,
755757
modifier: Modifier = Modifier,
756758
style: TextStyle = LocalTextStyle.current,
757-
delayPerChar: Int = 30,
758-
charAnimDuration: Int = 150,
759759
onAnimationFinished: () -> Unit = {},
760760
) {
761+
val charAnimDuration: Int = 150
761762
var visibleChars by remember { mutableStateOf(0) }
762763

764+
val charDelay = calculateCharDelay(text)
765+
763766
LaunchedEffect(visibleState.targetState, text) {
764767
if (visibleState.targetState) {
765768
visibleChars = 0
766-
repeat(text.length) { index ->
767-
delay(delayPerChar.toLong())
769+
text.toCharArray().forEachIndexed { index, char ->
768770
visibleChars = index + 1
771+
val specialCharDelayMultiplier = when (char) {
772+
',' -> 5
773+
in listOf('.', '?', '!', '\n', '\t') -> 10
774+
else -> 1
775+
}
776+
delay(charDelay * specialCharDelayMultiplier)
769777
}
770778
delay(charAnimDuration.toLong())
771779
onAnimationFinished()
@@ -800,6 +808,44 @@ private fun AnimatedRevealText(
800808
)
801809
}
802810

811+
/**
812+
* Speed multiplier decreases for longer text to avoid tedious animations
813+
* Short text (≤50 chars): full speed (1.0x multiplier)
814+
* Medium text (50-450 chars): linear interpolation
815+
* Long text (≥450 chars): 5x faster (0.2x multiplier)
816+
*/
817+
@Composable
818+
private fun calculateCharDelay(text: String): Duration = remember(text) {
819+
val textLength = text.length
820+
val baseRegularDelayMillis = 20
821+
822+
val shortTextThreshold = 50
823+
val longTextThreshold = 350
824+
val slowestMultiplier = 1.0
825+
val fastestMultiplier = 0.2
826+
827+
// Extreme fallback for extreme cases
828+
val superLongThreshold = 800
829+
val superFastestMultiplier = 0.05
830+
831+
val speedMultiplier = when {
832+
textLength <= shortTextThreshold -> slowestMultiplier
833+
textLength >= superLongThreshold -> superFastestMultiplier
834+
textLength >= longTextThreshold -> fastestMultiplier
835+
else -> {
836+
val characterRange = longTextThreshold - shortTextThreshold
837+
val charactersAboveThreshold = textLength - shortTextThreshold
838+
val interpolationProgress = charactersAboveThreshold.toDouble() / characterRange
839+
slowestMultiplier - interpolationProgress * (slowestMultiplier - fastestMultiplier)
840+
}
841+
}.coerceIn(fastestMultiplier, slowestMultiplier)
842+
843+
val minimumDelayRatio = 0.2
844+
(baseRegularDelayMillis * speedMultiplier)
845+
.coerceAtLeast((baseRegularDelayMillis * minimumDelayRatio))
846+
.milliseconds
847+
}
848+
803849
@Composable
804850
private fun StepBottomContent(
805851
stepItem: ClaimIntentStep,

app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ internal fun ContentSelectChips(
149149

150150
StepContent.ContentSelectStyle.BINARY -> {
151151
Row(
152-
Modifier.fillMaxWidth(),
152+
modifier.fillMaxWidth(),
153153
horizontalArrangement = Arrangement.SpaceEvenly,
154154
) {
155155
for (item in options) {
@@ -287,9 +287,9 @@ internal fun YesNoBubble(
287287
stringResource(Res.string.GENERAL_NO),
288288
),
289289
)
290-
Column {
290+
Column(modifier) {
291291
Row(
292-
modifier = modifier.fillMaxWidth(),
292+
modifier = Modifier.fillMaxWidth(),
293293
verticalAlignment = Alignment.CenterVertically,
294294
horizontalArrangement = Arrangement.End,
295295
) {
@@ -312,7 +312,7 @@ internal fun YesNoBubble(
312312
if (errorText != null) {
313313
Spacer(Modifier.height(4.dp))
314314
Row(
315-
modifier = modifier.fillMaxWidth(),
315+
modifier = Modifier.fillMaxWidth(),
316316
verticalAlignment = Alignment.CenterVertically,
317317
horizontalArrangement = Arrangement.End,
318318
) {
@@ -349,14 +349,14 @@ internal fun SingleSelectBubbleWithDialog(
349349
},
350350
)
351351
}
352-
Column {
352+
Column(modifier) {
353353
HedvigBigCard(
354354
onClick = { showDialog = true },
355355
labelText = questionLabel,
356356
inputText = options.firstOrNull {
357357
it.id == selectedOptionId
358358
}?.text,
359-
modifier = modifier,
359+
modifier = Modifier.fillMaxWidth(),
360360
enabled = true,
361361
)
362362
AnimatedVisibility(errorText != null) {
@@ -395,7 +395,7 @@ internal fun MultiSelectBubbleWithDialog(
395395
buttonText = stringResource(Res.string.general_save_button),
396396
)
397397
}
398-
Column {
398+
Column(modifier) {
399399
HedvigBigCard(
400400
onClick = { showDialog = true },
401401
labelText = questionLabel,
@@ -404,7 +404,7 @@ internal fun MultiSelectBubbleWithDialog(
404404
else -> options.filter { it.id in selectedOptionIds }
405405
.joinToString(transform = RadioOption::text)
406406
},
407-
modifier = modifier,
407+
modifier = Modifier.fillMaxWidth(),
408408
enabled = true,
409409
)
410410
AnimatedVisibility(errorText != null) {
@@ -697,12 +697,12 @@ internal fun DateSelectBubble(
697697
modifier: Modifier = Modifier,
698698
errorText: String? = null,
699699
) {
700-
Column {
700+
Column(modifier) {
701701
DatePickerWithDialog(
702702
datePickerState,
703703
canInteract = true,
704704
startText = questionLabel ?: "",
705-
modifier = modifier,
705+
Modifier.fillMaxWidth()
706706
)
707707
AnimatedVisibility(
708708
errorText != null &&
@@ -818,7 +818,7 @@ internal fun ChatClaimSummaryTopContent(
818818
)
819819
Spacer(Modifier.height(8.dp))
820820
CompositionLocalProvider(LocalContentColor provides HedvigTheme.colorScheme.textSecondary) {
821-
Column(modifier) {
821+
Column(Modifier) {
822822
for (displayItem in displayItems) {
823823
HorizontalItemsWithMaximumSpaceTaken(
824824
spaceBetween = 8.dp,

0 commit comments

Comments
 (0)