Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ 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
import org.jetbrains.compose.resources.stringResource
Expand Down Expand Up @@ -754,18 +756,24 @@ private fun AnimatedRevealText(
visibleState: MutableTransitionState<Boolean>,
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) }

val charDelay = calculateCharDelay(text)

LaunchedEffect(visibleState.targetState, text) {
if (visibleState.targetState) {
visibleChars = 0
repeat(text.length) { index ->
delay(delayPerChar.toLong())
text.toCharArray().forEachIndexed { index, char ->
visibleChars = index + 1
val specialCharDelayMultiplier = when (char) {
',' -> 5
in listOf('.', '?', '!', '\n', '\t') -> 10
else -> 1
}
delay(charDelay * specialCharDelayMultiplier)
}
delay(charAnimDuration.toLong())
onAnimationFinished()
Expand Down Expand Up @@ -800,6 +808,44 @@ 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

// 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
val charactersAboveThreshold = textLength - shortTextThreshold
val interpolationProgress = charactersAboveThreshold.toDouble() / characterRange
slowestMultiplier - interpolationProgress * (slowestMultiplier - fastestMultiplier)
}
}.coerceIn(fastestMultiplier, slowestMultiplier)

val minimumDelayRatio = 0.2
(baseRegularDelayMillis * speedMultiplier)
.coerceAtLeast((baseRegularDelayMillis * minimumDelayRatio))
.milliseconds
}

@Composable
private fun StepBottomContent(
stepItem: ClaimIntentStep,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ internal fun ContentSelectChips(

StepContent.ContentSelectStyle.BINARY -> {
Row(
Modifier.fillMaxWidth(),
modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
) {
for (item in options) {
Expand Down Expand Up @@ -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,
) {
Expand All @@ -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,
) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -395,7 +395,7 @@ internal fun MultiSelectBubbleWithDialog(
buttonText = stringResource(Res.string.general_save_button),
)
}
Column {
Column(modifier) {
HedvigBigCard(
onClick = { showDialog = true },
labelText = questionLabel,
Expand All @@ -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) {
Expand Down Expand Up @@ -697,12 +697,12 @@ internal fun DateSelectBubble(
modifier: Modifier = Modifier,
errorText: String? = null,
) {
Column {
Column(modifier) {
DatePickerWithDialog(
datePickerState,
canInteract = true,
startText = questionLabel ?: "",
modifier = modifier,
Modifier.fillMaxWidth()
)
AnimatedVisibility(
errorText != null &&
Expand Down Expand Up @@ -818,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,
Expand Down
Loading