11package com.getcode.ui.components.text
22
3- import androidx.compose.animation.AnimatedContent
4- import androidx.compose.animation.EnterTransition
5- import androidx.compose.animation.ExitTransition
63import androidx.compose.animation.ExperimentalAnimationApi
7- import androidx.compose.animation.animateContentSize
8- import androidx.compose.animation.core.LinearOutSlowInEasing
94import androidx.compose.animation.core.animateDpAsState
10- import androidx.compose.animation.core.tween
115import androidx.compose.animation.expandHorizontally
126import androidx.compose.animation.expandVertically
137import androidx.compose.animation.fadeIn
@@ -18,7 +12,6 @@ import androidx.compose.animation.slideInHorizontally
1812import androidx.compose.animation.slideInVertically
1913import androidx.compose.animation.slideOutHorizontally
2014import androidx.compose.animation.slideOutVertically
21- import androidx.compose.animation.togetherWith
2215import androidx.compose.foundation.Image
2316import androidx.compose.foundation.layout.Arrangement
2417import androidx.compose.foundation.layout.IntrinsicSize
@@ -39,27 +32,31 @@ import androidx.compose.runtime.getValue
3932import androidx.compose.runtime.mutableStateListOf
4033import androidx.compose.runtime.mutableStateOf
4134import androidx.compose.runtime.remember
35+ import androidx.compose.runtime.rememberUpdatedState
4236import androidx.compose.runtime.setValue
4337import androidx.compose.ui.Alignment
4438import androidx.compose.ui.Modifier
4539import androidx.compose.ui.draw.clip
4640import androidx.compose.ui.graphics.Color
41+ import androidx.compose.ui.layout.Layout
4742import androidx.compose.ui.platform.LocalDensity
4843import androidx.compose.ui.res.painterResource
4944import androidx.compose.ui.text.TextStyle
45+ import androidx.compose.ui.text.font.FontFamily
5046import androidx.compose.ui.text.font.FontWeight
47+ import androidx.compose.ui.text.rememberTextMeasurer
5148import androidx.compose.ui.text.style.TextOverflow
52- import androidx.compose.ui.unit.Density
5349import androidx.compose.ui.unit.TextUnit
5450import androidx.compose.ui.unit.dp
5551import androidx.compose.ui.unit.sp
5652import com.getcode.theme.CodeTheme
5753import com.getcode.ui.components.R
58- import com.getcode.ui.components.text.AmountSizeStore.remember
5954import com.getcode.ui.components.text.NumberInputHelper.Companion.DECIMAL_SEPARATOR
6055import com.getcode.ui.components.text.NumberInputHelper.Companion.GROUPING_SEPARATOR
56+ import com.getcode.ui.components.text.animated.AnimatedPlaceholderDigit
57+ import com.getcode.ui.components.text.animated.Digit
58+ import com.getcode.ui.components.text.animated.NormalizedPrefixText
6159import java.util.Timer
62- import kotlin.collections.HashMap
6360import kotlin.concurrent.schedule
6461
6562interface AmountInputViewModel {
@@ -92,7 +89,6 @@ internal fun AmountTextAnimated(
9289 val density = LocalDensity .current
9390
9491 val staticX5 = CodeTheme .dimens.staticGrid.x5
95- val staticX8 = CodeTheme .dimens.staticGrid.x8
9692
9793 // Initialize visibility states based on uiModel
9894 val initialAmount = uiModel.amountData.amount
@@ -289,7 +285,7 @@ internal fun AmountTextAnimated(
289285 modifier = Modifier
290286 .fillMaxWidth()
291287 .padding(top = CodeTheme .dimens.grid.x2)
292- .height(IntrinsicSize .Max ),
288+ .height(IntrinsicSize .Min ),
293289 horizontalArrangement = Arrangement .Center ,
294290 verticalAlignment = Alignment .CenterVertically ,
295291 ) {
@@ -303,18 +299,14 @@ internal fun AmountTextAnimated(
303299 Image (
304300 modifier = Modifier
305301 .requiredSize(CodeTheme .dimens.grid.x7)
306- .clip(CircleShape )
307- .align(Alignment .CenterVertically ),
308- painter = painterResource(
309- currencyResId
310- ),
302+ .clip(CircleShape ),
303+ painter = painterResource(currencyResId),
311304 contentDescription = " "
312305 )
313306 if (isClickable) {
314307 Image (
315308 modifier = Modifier
316- .width(CodeTheme .dimens.grid.x4)
317- .align(Alignment .CenterVertically ),
309+ .width(CodeTheme .dimens.grid.x4),
318310 painter = painterResource(R .drawable.ic_dropdown),
319311 contentDescription = " "
320312 )
@@ -329,68 +321,71 @@ internal fun AmountTextAnimated(
329321 .width(CodeTheme .dimens.grid.x1)
330322 )
331323
332- Text (
333- modifier = Modifier .fillMaxHeight(),
334- text = amountPrefix,
335- fontSize = textSize,
336- fontWeight = FontWeight .Bold
337- )
338324
339- AnimatedPlaceholderDigit (
340- modifier = Modifier .fillMaxHeight(),
341- text = firstDigit,
342- digitVisible = digitVisibility[0 ],
343- placeholder = placeholder,
344- placeholderVisible = zeroVisibility,
345- fontSize = textSize,
346- density = density,
347- placeholderEnter = zeroEnter,
348- placeholderExit = zeroExit,
349- placeholderColor = Color .White
350- )
351-
352- for (i in 1 until maxDigits) {
353- Digit (
325+ NormalizedPrefixText (
326+ amountPrefix = amountPrefix,
327+ textStyle = textStyle,
328+ textSize = textSize,
329+ ) {
330+ AnimatedPlaceholderDigit (
354331 modifier = Modifier .fillMaxHeight(),
355- isVisible = getComma(i = i),
356- text = GROUPING_SEPARATOR .toString(),
332+ text = firstDigit,
333+ digitVisible = digitVisibility[0 ],
334+ placeholder = placeholder,
335+ placeholderVisible = zeroVisibility,
357336 fontSize = textSize,
358337 density = density,
338+ placeholderEnter = zeroEnter,
339+ placeholderExit = zeroExit,
340+ placeholderColor = Color .White
359341 )
360- Digit (
361- modifier = Modifier .fillMaxHeight(),
362- isVisible = digitVisibility[i],
363- text = getValue(0 , i) ? : getLastValue(0 , i) ? : " " ,
364- fontSize = textSize,
365- density = density
366- )
367- }
368342
369- Digit (
370- modifier = Modifier .fillMaxHeight(),
371- isVisible = decimalPointVisibility,
372- text = DECIMAL_SEPARATOR .toString(),
373- fontSize = textSize,
374- density = density,
375- enter = decimalEnter,
376- exit = decimalExit,
377- )
343+ for (i in 1 until maxDigits) {
344+ Digit (
345+ modifier = Modifier .fillMaxHeight(),
346+ isVisible = getComma(i = i),
347+ text = GROUPING_SEPARATOR .toString(),
348+ fontSize = textSize,
349+ density = density,
350+ )
351+ Digit (
352+ modifier = Modifier .fillMaxHeight(),
353+ isVisible = digitVisibility[i],
354+ text = getValue(0 , i) ? : getLastValue(0 , i) ? : " " ,
355+ fontSize = textSize,
356+ density = density
357+ )
358+ }
378359
379- for (i in 0 until totalDecimals) {
380- AnimatedPlaceholderDigit (
381- text = getValue(1 , i) ? : getLastValue(1 , i) ? : " 0" ,
382- digitVisible = digitDecimalVisibility[i],
383- placeholderVisible = digitDecimalZeroVisibility[i],
384- placeholder = " 0" ,
360+ Digit (
361+ modifier = Modifier .fillMaxHeight(),
362+ isVisible = decimalPointVisibility,
363+ text = DECIMAL_SEPARATOR .toString(),
385364 fontSize = textSize,
386365 density = density,
387- placeholderEnter = decimalZeroEnter ,
388- placeholderExit = decimalZeroExit ,
366+ enter = decimalEnter ,
367+ exit = decimalExit ,
389368 )
369+
370+ for (i in 0 until totalDecimals) {
371+ AnimatedPlaceholderDigit (
372+ text = getValue(1 , i) ? : getLastValue(1 , i) ? : " 0" ,
373+ digitVisible = digitDecimalVisibility[i],
374+ placeholderVisible = digitDecimalZeroVisibility[i],
375+ placeholder = " 0" ,
376+ fontSize = textSize,
377+ density = density,
378+ placeholderEnter = decimalZeroEnter,
379+ placeholderExit = decimalZeroExit,
380+ modifier = Modifier .fillMaxHeight(),
381+ )
382+ }
390383 }
391384
392385 Text (
393- modifier = Modifier .padding(end = CodeTheme .dimens.grid.x3),
386+ modifier = Modifier
387+ .fillMaxHeight()
388+ .padding(end = CodeTheme .dimens.grid.x3),
394389 text = amountSuffix.ifEmpty { " " },
395390 fontSize = textSize,
396391 fontWeight = FontWeight .Bold ,
@@ -406,142 +401,4 @@ internal fun AmountTextAnimated(
406401 }
407402}
408403
409- private fun defaultDigitEnter (initialOffsetY : Int ): EnterTransition {
410- return (slideInVertically(
411- initialOffsetY = { initialOffsetY },
412- animationSpec = tween(
413- durationMillis = 300 ,
414- delayMillis = 80 ,
415- easing = LinearOutSlowInEasing
416- )
417- ) + fadeIn())
418- }
419-
420- @Composable
421- private fun defaultDigitExit (targetOffsetY : Int ): ExitTransition {
422- return fadeOut() + slideOutVertically(
423- targetOffsetY = { targetOffsetY },
424- animationSpec = tween(durationMillis = 300 , easing = LinearOutSlowInEasing )
425- )
426- }
427-
428- @Composable
429- private fun Digit (
430- isVisible : Boolean ,
431- text : String ,
432- fontSize : TextUnit ,
433- density : Density ,
434- modifier : Modifier = Modifier ,
435- enter : EnterTransition ? = null,
436- exit : ExitTransition ? = null,
437- color : Color = Color .White ,
438- ) {
439- val staticX4 = CodeTheme .dimens.staticGrid.x4
440- val defaultEnter = defaultDigitEnter(initialOffsetY = with (density) { - (staticX4).roundToPx() })
441- val enterTransition = remember(enter) {
442- enter ? : defaultEnter
443- }
444-
445- val defaultExit = defaultDigitExit(targetOffsetY = with (density) { - (staticX4).roundToPx() })
446- val exitTransition = remember(exit) {
447- exit ? : defaultExit
448- }
449-
450- Row (
451- modifier = modifier,
452- ) {
453- AnimatedContent (
454- targetState = isVisible,
455- transitionSpec = { enterTransition togetherWith exitTransition }
456- ) { visible ->
457- if (visible) {
458- Text (
459- text = text,
460- fontSize = fontSize,
461- fontWeight = FontWeight .Bold ,
462- color = color
463- )
464- } else {
465- Spacer (modifier = Modifier )
466- }
467- }
468- }
469- }
470-
471-
472- private enum class PlaceholderState {
473- Digit , Placeholder , None
474- }
475-
476- @Composable
477- private fun AnimatedPlaceholderDigit (
478- text : String ,
479- placeholder : String ,
480- digitVisible : Boolean ,
481- placeholderVisible : Boolean ,
482- fontSize : TextUnit ,
483- density : Density ,
484- modifier : Modifier = Modifier ,
485- placeholderEnter : EnterTransition ,
486- placeholderExit : ExitTransition ,
487- placeholderColor : Color = Color .White .copy(alpha = .2f),
488- ) {
489- val targetState = when {
490- digitVisible -> PlaceholderState .Digit
491- placeholderVisible -> PlaceholderState .Placeholder
492- else -> PlaceholderState .None
493- }
494-
495- val staticX4 = CodeTheme .dimens.staticGrid.x4
496- val digitEnter = defaultDigitEnter(initialOffsetY = with (density) { - (staticX4).roundToPx() })
497- val digitExit = defaultDigitExit(targetOffsetY = with (density) { - (staticX4).roundToPx() })
498-
499- AnimatedContent (
500- targetState = targetState,
501- transitionSpec = {
502- when {
503- // Placeholder -> Digit: Placeholder exits, Digit enters
504- initialState == PlaceholderState .Placeholder && targetState == PlaceholderState .Digit -> {
505- digitEnter togetherWith placeholderExit
506- }
507- // Digit -> Placeholder: Digit exits, Placeholder enters
508- initialState == PlaceholderState .Digit && targetState == PlaceholderState .Placeholder -> {
509- placeholderEnter togetherWith digitExit
510- }
511-
512- // None -> Placeholder: None exits, Placeholder enters
513- initialState == PlaceholderState .None && targetState == PlaceholderState .Placeholder -> {
514- placeholderEnter togetherWith ExitTransition .None
515- }
516-
517- else -> {
518- EnterTransition .None togetherWith ExitTransition .None
519- }
520- }
521- },
522- modifier = modifier
523- ) { state ->
524- when (state) {
525- PlaceholderState .Digit -> {
526- Text (
527- text = text,
528- fontSize = fontSize,
529- fontWeight = FontWeight .Bold ,
530- color = Color .White
531- )
532- }
533- PlaceholderState .Placeholder -> {
534- Text (
535- text = placeholder,
536- fontSize = fontSize,
537- fontWeight = FontWeight .Bold ,
538- color = placeholderColor
539- )
540- }
541-
542- PlaceholderState .None -> Unit
543- }
544- }
545- }
546-
547404private val AnimationMaxY = 120 .dp
0 commit comments