Updated on 2026-08-14

This commit is contained in:
Tangem 2022-06-08 16:12:43 +03:00
parent 0df972955a
commit 4356cce18a
11 changed files with 109 additions and 103 deletions

View file

@ -1,11 +1,8 @@
package com.tangem.tap.features.home.compose.uiTools
package com.tangem.tap.features.home.compose
import androidx.annotation.DrawableRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.animation.core.*
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.runtime.Composable
@ -16,16 +13,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.tangem.tap.common.compose.extensions.AnimatedValue
import com.tangem.tap.common.compose.extensions.toAnimatable
@Composable
fun HorizontalSlidingImage(
@ -37,32 +32,20 @@ fun HorizontalSlidingImage(
targetOffset: Float,
contentDescription: String,
) {
val offsetX = remember { Animatable(startOffset * -1f) }
val translateX = AnimatedValue(startOffset * -1f, (startOffset + targetOffset) * -1f)
Image(
modifier = Modifier
.offset { IntOffset(offsetX.value.toInt(), 0) }
.requiredWidth(itemSize.width)
.requiredHeight(itemSize.height),
.requiredHeight(itemSize.height)
.graphicsLayer(
translationX = translateX.toAnimatable(isPaused = paused, duration = duration).value
),
alignment = Alignment.TopStart,
contentScale = ContentScale.FillBounds,
painter = painter,
contentDescription = contentDescription,
)
LaunchedEffect(paused) {
if (paused) {
offsetX.stop()
} else {
offsetX.animateTo(
targetValue = (startOffset + targetOffset) * -1f,
animationSpec = tween(
durationMillis = duration,
easing = LinearEasing,
),
)
}
}
}
@Composable
@ -109,7 +92,7 @@ fun StoriesTextAnimation(
@Composable
fun StoriesBottomImageAnimation(
initialScale: Float = 2f,
initialScale: Float = 2.5f,
firstStepDuration: Int,
totalDuration: Int,
content: @Composable (Modifier) -> Unit
@ -120,18 +103,10 @@ fun StoriesBottomImageAnimation(
val isFirstStepLaunched = remember { mutableStateOf(false) }
val isSecondStepLaunched = remember { mutableStateOf(false) }
val firstTransition = updateTransition(targetState = isFirstStepLaunched.value, label = "Bottom image animation")
val secondTransition = updateTransition(targetState = isSecondStepLaunched.value, label = "Bottom image animation")
val fadeIn = firstTransition.animateFloat(
transitionSpec = {
tween(
durationMillis = 400,
)
},
label = "Fade in animation"
) { value -> if (value) 1f else 0f }
val firstTransition = updateTransition(
targetState = isFirstStepLaunched.value,
label = "Image appearing"
)
val firstScaleStep = firstTransition.animateFloat(
transitionSpec = {
tween(
@ -139,10 +114,13 @@ fun StoriesBottomImageAnimation(
easing = FastOutLinearInEasing,
)
},
label = "Scale"
label = "Appearing scale"
) { value -> if (value) scaleSwitchBarrier else initialScale }
val secondTransition = updateTransition(
targetState = isSecondStepLaunched.value,
label = "Image slow outgoing"
)
val secondScaleStep = secondTransition.animateFloat(
transitionSpec = {
tween(
@ -150,10 +128,17 @@ fun StoriesBottomImageAnimation(
easing = LinearEasing,
)
},
label = "Scale"
label = "Outgoing scale"
) { value -> if (value) 1f else scaleSwitchBarrier }
if (firstScaleStep.value == scaleSwitchBarrier) isSecondStepLaunched.value = true
val fadeIn = firstTransition.animateFloat(
transitionSpec = { tween(durationMillis = 400) },
label = "Fade in on start"
) { value -> if (value) 1f else 0f }
if (firstScaleStep.value == scaleSwitchBarrier) {
isSecondStepLaunched.value = true
}
val modifier = if (!isSecondStepLaunched.value) {
Modifier.scale(firstScaleStep.value)
@ -164,11 +149,4 @@ fun StoriesBottomImageAnimation(
content(modifier)
LaunchedEffect(Unit) { isFirstStepLaunched.value = true }
}
@Composable
fun asImageBitmap(@DrawableRes drawableId: Int): ImageBitmap {
val drawable = AppCompatResources.getDrawable(LocalContext.current, drawableId)
?: throw NullPointerException()
return drawable.toBitmap().asImageBitmap()
}
}

View file

@ -1,8 +1,7 @@
package com.tangem.tap.features.home.compose.uiTools
package com.tangem.tap.features.home.compose
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.compose.content.*
/**

View file

@ -26,8 +26,8 @@ import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tap.common.compose.SpacerH
import com.tangem.tap.common.compose.SpacerH16
import com.tangem.tap.common.compose.extensions.toAndroidGraphicsColor
import com.tangem.tap.features.home.compose.uiTools.StoriesBottomImageAnimation
import com.tangem.tap.features.home.compose.uiTools.StoriesTextAnimation
import com.tangem.tap.features.home.compose.StoriesBottomImageAnimation
import com.tangem.tap.features.home.compose.StoriesTextAnimation
import com.tangem.wallet.R
@Composable
@ -237,7 +237,7 @@ private fun HtmlText(
}
@Composable
fun StoriesImage(
private fun StoriesImage(
modifier: Modifier = Modifier,
@DrawableRes drawableResId: Int,
isDarkBackground: Boolean,

View file

@ -11,19 +11,17 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tangem_sdk_new.extensions.pxToDp
import com.tangem.tap.common.compose.SpacerH
import com.tangem.tap.common.compose.extensions.dpSize
import com.tangem.tap.common.compose.extensions.halfHeight
import com.tangem.tap.common.compose.extensions.toPx
import com.tangem.tap.common.extensions.isEven
import com.tangem.tap.features.home.compose.uiTools.HorizontalSlidingImage
import com.tangem.tap.features.home.compose.HorizontalSlidingImage
import com.tangem.wallet.R
@Composable
@ -89,7 +87,7 @@ fun StoriesWeb3Content(
}
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
val decreaseRate = remember { 1f / dappsItemList.size }
val designItemHeight = 50.dp
val designItemHeight = 75.dp
LightenBox {
Column(modifier = Modifier.graphicsLayer(clip = false)) {
@ -101,7 +99,7 @@ fun StoriesWeb3Content(
val chessOffset = if (index.isEven()) 0.dp else scaledItemSize.width / 3
val animateFrom = chessOffset - moveItemToStartOfScreen
val animateTo = 90.dp - (90.dp * index * decreaseRate)
val animateTo = 70.dp - (70.dp * index * decreaseRate)
HorizontalSlidingImage(
paused = paused,
@ -112,7 +110,6 @@ fun StoriesWeb3Content(
targetOffset = animateTo.toPx(),
contentDescription = "Web3 row",
)
SpacerH(14.dp)
}
}
@ -126,11 +123,13 @@ private fun LightenBox(content: @Composable () -> Unit) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(top = 170.dp)
.padding(top = 250.dp)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.White.copy(alpha = 0.3f),
Color.White.copy(alpha = 0f),
Color.White.copy(alpha = 0.75f),
Color.White.copy(alpha = 0.95f),
Color.White
)
)
@ -139,23 +138,7 @@ private fun LightenBox(content: @Composable () -> Unit) {
}
}
@Composable
private fun Painter.dpSize(): DpSize = DpSize(
intrinsicSize.width.pxToDp().dp,
intrinsicSize.height.pxToDp().dp,
)
@Composable
private fun Float.dpToPx(): Float = LocalContext.current.dpToPx(this)
@Composable
private fun Float.pxToDp(): Float = LocalContext.current.pxToDp(this)
private fun scaleToDesignSize(itemSize: DpSize, designItemHeight: Dp): DpSize {
val scaleRate = itemSize.height / designItemHeight
return itemSize / scaleRate
}
private fun DpSize.halfWidth(): Dp = this.width / 2
private fun DpSize.halfHeight(): Dp = this.height / 2
}

View file

@ -19,10 +19,10 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.tap.features.home.compose.uiTools.StoriesBottomImageAnimation
import com.tangem.tap.features.home.compose.uiTools.StoriesTextAnimation
import com.tangem.tap.features.home.compose.views.FontSizeRange
import com.tangem.tap.features.home.compose.views.TextAutoSize
import com.tangem.tap.common.compose.FontSizeRange
import com.tangem.tap.common.compose.TextAutoSize
import com.tangem.tap.features.home.compose.StoriesBottomImageAnimation
import com.tangem.tap.features.home.compose.StoriesTextAnimation
import com.tangem.wallet.R
@Composable
@ -113,7 +113,7 @@ fun FirstStoriesContent(
) {
StoriesBottomImageAnimation(
totalDuration = duration,
firstStepDuration = 500,
firstStepDuration = 400,
) { modifier ->
Image(
modifier = modifier.fillMaxWidth(),
@ -128,7 +128,7 @@ fun FirstStoriesContent(
}
}
enum class StartingScreenState {
private enum class StartingScreenState {
INIT, BUY, STORE, SEND, PAY, EXCHANGE, BORROW, LEND, SHOW_CARD, MEET_TANGEM
}
@ -151,11 +151,6 @@ private fun MutableState<StartingScreenState>.isSplashingTextDisplaying(): Boole
this.value != StartingScreenState.SHOW_CARD
}
private fun MutableState<StartingScreenState>.isTangemCardDisplaying(): Boolean {
return this.value == StartingScreenState.SHOW_CARD ||
this.value == StartingScreenState.MEET_TANGEM
}
private fun MutableState<StartingScreenState>.isMeetTangemDisplaying(): Boolean {
return this.value == StartingScreenState.MEET_TANGEM
}

View file

@ -6,9 +6,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import com.tangem.tap.features.home.compose.uiTools.AnimatedValue
import com.tangem.tap.features.home.compose.uiTools.asImageBitmap
import com.tangem.tap.features.home.compose.uiTools.toAnimatable
import com.tangem.tap.common.compose.extensions.AnimatedValue
import com.tangem.tap.common.compose.extensions.asImageBitmap
import com.tangem.tap.common.compose.extensions.toAnimatable
import com.tangem.wallet.R
/**
@ -84,7 +84,7 @@ private class FloatingCard {
fun second(): CardValues = CardValues(
translateX = 350f to 300f,
translateY = 0f to 50f,
translateY = -70f to 0f,
rotationX = 30f to 48f,
rotationY = 0f to 5f,
rotationZ = -34f to -42f,
@ -97,7 +97,7 @@ private class FloatingCard {
rotationX = 0f to 3f,
rotationY = 10f to 10f,
rotationZ = -45f to -30f,
scale = 0.6f to 0.7f,
scale = 0.6f to 0.75f,
)
}
}

View file

@ -1,47 +0,0 @@
package com.tangem.tap.features.home.compose.uiTools
import androidx.compose.animation.core.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
typealias AnimatedValue = Pair<Float, Float>
@Composable
fun AnimatedValue.toAnimatable(
isPaused: Boolean,
duration: Int,
easing: Easing = LinearEasing,
): Animatable<Float, AnimationVector1D> {
return animatable(
values = this,
isPaused = isPaused,
duration = duration,
easing = easing,
)
}
@Composable
fun animatable(
values: AnimatedValue,
isPaused: Boolean,
duration: Int,
easing: Easing = LinearEasing,
): Animatable<Float, AnimationVector1D> {
val animatable = remember { Animatable(values.first) }
LaunchedEffect(isPaused) {
if (isPaused) {
animatable.stop()
} else {
animatable.animateTo(
targetValue = values.second,
animationSpec = tween(
durationMillis = duration,
easing = easing
)
)
}
}
return animatable
}

View file

@ -1,66 +0,0 @@
package com.tangem.tap.features.home.compose.views
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
@Composable
fun TextAutoSize(
modifier: Modifier = Modifier,
text: String,
textStyle: TextStyle = LocalTextStyle.current,
fontSizeRange: FontSizeRange,
) {
val fontSizeValue = remember { mutableStateOf(fontSizeRange.max.value) }
val readyToDraw = remember { mutableStateOf(false) }
val textState = remember { mutableStateOf(text) }
if (textState.value != text) {
readyToDraw.value = false
fontSizeValue.value = fontSizeRange.max.value
textState.value = text
}
Text(
modifier = modifier.drawWithContent { if (readyToDraw.value) drawContent() },
text = text,
softWrap = false,
style = textStyle,
fontSize = fontSizeValue.value.sp,
onTextLayout = {
if (it.hasVisualOverflow) {
val nextFontSizeValue = fontSizeValue.value - fontSizeRange.step.value
if (nextFontSizeValue <= fontSizeRange.min.value) {
fontSizeValue.value = fontSizeRange.min.value
readyToDraw.value = true
} else {
fontSizeValue.value = nextFontSizeValue * 0.8f
}
} else {
readyToDraw.value = true
}
}
)
}
data class FontSizeRange(
val min: TextUnit,
val max: TextUnit,
val step: TextUnit = DEFAULT_TEXT_STEP,
) {
init {
require(min < max) { "min should be less than max, $this" }
require(step.value > 0) { "step should be greater than 0, $this" }
}
companion object {
private val DEFAULT_TEXT_STEP = 1.sp
}
}