diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index f50133547b..64ae04d208 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit f50133547b5e19fb7ad32e64370573d60f6791fd +Subproject commit 64ae04d2086e5a605dd4da3cba4af32a60d46c79 diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/AutoSizeText.kt b/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt similarity index 93% rename from app/src/main/java/com/tangem/tap/features/home/compose/AutoSizeText.kt rename to app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt index 2bb6c5b8d5..324fd3877e 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/AutoSizeText.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt @@ -1,6 +1,5 @@ -package com.tangem.tap.features.home.compose +package com.tangem.tap.common.compose -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.material.LocalTextStyle import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -12,7 +11,6 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.sp -@OptIn(ExperimentalAnimationApi::class) @Composable fun TextAutoSize( modifier: Modifier = Modifier, diff --git a/app/src/main/java/com/tangem/tap/common/compose/Spacer.kt b/app/src/main/java/com/tangem/tap/common/compose/Spacer.kt index b31a15c4ae..ce0b190598 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/Spacer.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/Spacer.kt @@ -38,6 +38,11 @@ fun SpacerH24(modifier: Modifier = Modifier) { SpacerH(24.dp, modifier) } +@Composable +fun SpacerH32(modifier: Modifier = Modifier) { + SpacerH(32.dp, modifier) +} + // ***************************** Vertical @Composable fun SpacerW(width: Dp, modifier: Modifier = Modifier) { @@ -64,6 +69,11 @@ fun SpacerW24(modifier: Modifier = Modifier) { SpacerW(24.dp, modifier) } +@Composable +fun SpacerW32(modifier: Modifier = Modifier) { + SpacerW(32.dp, modifier) +} + // ***************************** Size @Composable fun SpacerS(size: Dp, modifier: Modifier = Modifier) { @@ -88,4 +98,9 @@ fun SpacerS16(modifier: Modifier = Modifier) { @Composable fun SpacerS24(modifier: Modifier = Modifier) { SpacerS(24.dp, modifier) +} + +@Composable +fun SpacerS32(modifier: Modifier = Modifier) { + SpacerS(32.dp, modifier) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/AnimatedValue.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/AnimatedValue.kt new file mode 100644 index 0000000000..1952c6a058 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/AnimatedValue.kt @@ -0,0 +1,50 @@ +package com.tangem.tap.common.compose.extensions + +import androidx.compose.animation.core.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember + +/** +[REDACTED_AUTHOR] + */ +typealias AnimatedValue = Pair + +@Composable +fun AnimatedValue.toAnimatable( + isPaused: Boolean, + duration: Int, + easing: Easing = LinearEasing, +): Animatable { + return animatable( + values = this, + isPaused = isPaused, + duration = duration, + easing = easing, + ) +} + +@Composable +fun animatable( + values: AnimatedValue, + duration: Int, + isPaused: Boolean = false, + easing: Easing = LinearEasing, +): Animatable { + 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 +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/Dp.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/Dp.kt new file mode 100644 index 0000000000..42acdcf306 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/Dp.kt @@ -0,0 +1,19 @@ +package com.tangem.tap.common.compose.extensions + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpSize + +/** +[REDACTED_AUTHOR] + */ +@Composable +fun Dp.toPx(): Float { + val currentDp = this + return with(LocalDensity.current) { currentDp.toPx() } +} + +fun DpSize.halfWidth(): Dp = this.width / 2 + +fun DpSize.halfHeight(): Dp = this.height / 2 \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt new file mode 100644 index 0000000000..1692b4c51f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt @@ -0,0 +1,19 @@ +package com.tangem.tap.common.compose.extensions + +import androidx.annotation.DrawableRes +import androidx.appcompat.content.res.AppCompatResources +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalContext +import androidx.core.graphics.drawable.toBitmap + +/** +[REDACTED_AUTHOR] + */ +@Composable +fun asImageBitmap(@DrawableRes drawableId: Int): ImageBitmap { + val drawable = AppCompatResources.getDrawable(LocalContext.current, drawableId) + ?: throw NullPointerException() + return drawable.toBitmap().asImageBitmap() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/Painter.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/Painter.kt new file mode 100644 index 0000000000..fa91934a30 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/Painter.kt @@ -0,0 +1,24 @@ +package com.tangem.tap.common.compose.extensions + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalContext +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 + +/** +[REDACTED_AUTHOR] + */ +@Composable +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) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt index 38b9d9fa48..617f69c36d 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt @@ -93,32 +93,39 @@ fun View.invisible(invisible: Boolean = true, invokeBeforeStateChanged: (() -> U } fun Context.dpToPixels(dp: Int): Int = - TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics - ).toInt() + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics + ).toInt() fun Context.pixelsToDp(pixels: Int): Int { return (pixels.toFloat() / - (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)) + (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)) .toInt() } +fun Context.dpToPixels(dp: Float): Float = + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, this.resources.displayMetrics) + + +fun Context.pixelsToDp(pixels: Float): Float = + (pixels / (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)) + tailrec fun Context?.getActivity(): Activity? = this as? Activity - ?: (this as? ContextWrapper)?.baseContext?.getActivity() + ?: (this as? ContextWrapper)?.baseContext?.getActivity() fun MaterialCardView.setMargins( - marginLeftDp: Int = 16, - marginTopDp: Int = 8, - marginRightDp: Int = 16, - marginBottomDp: Int = 8 + marginLeftDp: Int = 16, + marginTopDp: Int = 8, + marginRightDp: Int = 16, + marginBottomDp: Int = 8 ) { val params = this.layoutParams (params as ViewGroup.MarginLayoutParams).setMargins( - context.dpToPixels(marginLeftDp), - context.dpToPixels(marginTopDp), - context.dpToPixels(marginRightDp), - context.dpToPixels(marginBottomDp) + context.dpToPixels(marginLeftDp), + context.dpToPixels(marginTopDp), + context.dpToPixels(marginRightDp), + context.dpToPixels(marginBottomDp) ) this.layoutParams = params } @@ -128,11 +135,11 @@ fun Activity.setSystemBarTextColor(setTextDark: Boolean) { val flags = this.window.decorView.systemUiVisibility // Update the SystemUiVisibility dependening on whether we want a Light or Dark theme. this.window.decorView.systemUiVisibility = - if (setTextDark) { - flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() - } else { - flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - } + if (setTextDark) { + flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() + } else { + flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } } } @@ -150,7 +157,7 @@ fun Context.copyToClipboard(value: Any, label: String = "") { fun Context.getFromClipboard(default: CharSequence? = null): CharSequence? { val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager - ?: return default + ?: return default val clipData = clipboard.primaryClip ?: return default if (clipData.itemCount == 0) return default @@ -172,10 +179,10 @@ fun Fragment.shareText(text: String) { } fun Context.safeStartActivity( - intent: Intent, - options: Bundle? = null, - fallback: ((ActivityNotFoundException) -> Unit)? = null, - finally: VoidCallback? = null + intent: Intent, + options: Bundle? = null, + fallback: ((ActivityNotFoundException) -> Unit)? = null, + finally: VoidCallback? = null ) { try { this.startActivity(intent, options) diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/FirstStoriesContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/FirstStoriesContent.kt deleted file mode 100644 index 30e72ec626..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/compose/FirstStoriesContent.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.tangem.tap.features.home.compose - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.tween -import androidx.compose.animation.scaleIn -import androidx.compose.animation.slideInVertically -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.tangem.wallet.R - -@OptIn(ExperimentalAnimationApi::class) -@Composable -fun FirstStoriesContent( - paused: Boolean, duration: Int = 8_000, - hideContent: (Boolean) -> Unit -) { - val screenState = remember { mutableStateOf(StartingScreenState.INIT) } - val progress = remember { Animatable(0f) } - - LaunchedEffect(paused) { - if (paused) { - progress.stop() - } else { - progress.animateTo( - targetValue = 2f, - animationSpec = tween( - durationMillis = duration, - easing = LinearEasing - ) - ) - } - } - - when (progress.value) { - in 0f..0.2f -> screenState.value = StartingScreenState.INIT - in 0.2f..0.3f -> screenState.value = StartingScreenState.BUY - in 0.3f..0.4f -> screenState.value = StartingScreenState.STORE - in 0.4f..0.5f -> screenState.value = StartingScreenState.SEND - in 0.5f..0.6f -> screenState.value = StartingScreenState.PAY - in 0.6f..0.7f -> screenState.value = StartingScreenState.EXCHANGE - in 0.7f..0.8f -> screenState.value = StartingScreenState.BORROW - in 0.8f..1f -> screenState.value = StartingScreenState.LEND - in 1f..1.2f -> screenState.value = StartingScreenState.SHOW_CARD - in 1.2f..2f -> screenState.value = StartingScreenState.MEET_TANGEM - } - - if (screenState.value == StartingScreenState.INIT) hideContent(true) - if (screenState.value == StartingScreenState.BUY) hideContent(false) - - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = - Modifier - .fillMaxSize(), - ) { - - val text = when (screenState.value) { - StartingScreenState.INIT -> null - StartingScreenState.BUY -> R.string.story_meet_buy - StartingScreenState.STORE -> R.string.story_meet_store - StartingScreenState.SEND -> R.string.story_meet_send - StartingScreenState.PAY -> R.string.story_meet_pay - StartingScreenState.EXCHANGE -> R.string.story_meet_exchange - StartingScreenState.BORROW -> R.string.story_meet_borrow - StartingScreenState.LEND -> R.string.story_meet_lend - StartingScreenState.SHOW_CARD -> R.string.story_meet_title - StartingScreenState.MEET_TANGEM -> R.string.story_meet_title - } - - val style = TextStyle( - fontSize = 60.sp, - fontWeight = FontWeight.SemiBold, - color = Color.White, - textAlign = TextAlign.Center, - ) - - if (screenState.value != StartingScreenState.MEET_TANGEM) { - TextAutoSize( - modifier = Modifier - .padding(start = 20.dp, end = 20.dp, bottom = 100.dp) - .alpha(if (screenState.value == StartingScreenState.SHOW_CARD) 0f else 1f), - text = text?.let { stringResource(text) } ?: "", - textStyle = style, - fontSizeRange = FontSizeRange(20.sp, 60.sp) - ) - } - - AnimatedVisibility( - visible = screenState.value == StartingScreenState.MEET_TANGEM, - enter = slideInVertically() { it / 2 } - ) { - TextAutoSize( - modifier = Modifier - .fillMaxWidth() - .padding(start = 40.dp, end = 40.dp), - text = text?.let { stringResource(text) } ?: "", - textStyle = style, - fontSizeRange = FontSizeRange(20.sp, 60.sp) - ) - } - - AnimatedVisibility( - visible = screenState.value == StartingScreenState.SHOW_CARD || - screenState.value == StartingScreenState.MEET_TANGEM, - enter = scaleIn(initialScale = 3f) - ) { - Image( - painter = painterResource( - id = R.drawable.meet_tangem - ), - contentDescription = null, - contentScale = ContentScale.FillWidth, - modifier = Modifier.fillMaxWidth() - ) - } - } -} - -enum class StartingScreenState { - INIT, BUY, STORE, SEND, PAY, EXCHANGE, BORROW, LEND, SHOW_CARD, MEET_TANGEM -} - - -@Preview -@Composable -fun Stories1Preview() { - FirstStoriesContent(false, 7_000) {} -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt b/app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt index 4bfac1e8a0..183392a4b4 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt @@ -24,8 +24,11 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.tap.common.compose.SpacerS24 import androidx.compose.ui.unit.sp +import com.tangem.tap.common.compose.SpacerS24 +import com.tangem.tap.features.home.compose.content.* +import com.tangem.tap.features.home.compose.views.HomeButtons +import com.tangem.tap.features.home.compose.views.StoriesProgressBar import com.tangem.tap.features.home.redux.HomeState import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.wallet.R @@ -39,8 +42,6 @@ fun StoriesScreen( onSearchTokensClick: () -> Unit, ) { val steps = 6 - val stepDuration = 8_000 - val currentStep = remember { mutableStateOf(1) } val isDarkBackground = currentStep.value !in 3..5 @@ -55,7 +56,7 @@ fun StoriesScreen( val isPressed = remember { mutableStateOf(false) } val needsToBePaused = remember(homeState) { mutableStateOf(homeState.value.btnScanState.progressState == ProgressState.Loading) } - val pause = isPressed.value || needsToBePaused.value + val isPaused = isPressed.value || needsToBePaused.value val hideContent = remember { mutableStateOf(true) } @@ -122,9 +123,8 @@ fun StoriesScreen( StoriesProgressBar( steps = steps, currentStep = currentStep.value, -// paused = isPressed.value, - stepDuration = stepDuration, - paused = pause, + stepDuration = currentStep.duration(), + paused = isPaused, onStepFinished = goToNextScreen, ) Image( @@ -139,12 +139,12 @@ fun StoriesScreen( colorFilter = if (isDarkBackground) null else ColorFilter.tint(Color.Black) ) when (currentStep.value) { - 1 -> FirstStoriesContent(pause, stepDuration) { hideContent.value = it } - 2 -> StoriesRevolutionaryWallet() - 3 -> StoriesUltraSecureBackup() - 4 -> StoriesThousandsOfCurrencies() - 5 -> StoriesWeb3() - 6 -> StoriesWalletForEveryone() + 1 -> FirstStoriesContent(isPaused, currentStep.duration()) { hideContent.value = it } + 2 -> StoriesRevolutionaryWallet(currentStep.duration()) + 3 -> StoriesUltraSecureBackup(isPaused, currentStep.duration()) + 4 -> StoriesCurrencies(isPaused, currentStep.duration()) + 5 -> StoriesWeb3(isPaused, currentStep.duration()) + 6 -> StoriesWalletForEveryone(currentStep.duration()) } } Column( @@ -157,8 +157,7 @@ fun StoriesScreen( modifier = Modifier .fillMaxWidth() .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) - .height(48.dp) - , + .height(48.dp), colors = ButtonDefaults.textButtonColors( backgroundColor = Color.White, contentColor = Color(0xFF080C10) @@ -188,9 +187,13 @@ fun StoriesScreen( } } +private fun MutableState.duration(): Int = when (this.value) { + 1 -> 8000 + else -> 6000 +} @Preview @Composable -fun InstagramScreenPreview() { +fun StoriesScreenPreview() { StoriesScreen(onScanButtonClick = {}, onShopButtonClick = {}, onSearchTokensClick = {}) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt new file mode 100644 index 0000000000..58f9356349 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt @@ -0,0 +1,152 @@ +package com.tangem.tap.features.home.compose + +import androidx.compose.animation.core.* +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.absoluteOffset +import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.requiredWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +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.graphicsLayer +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import com.tangem.tap.common.compose.extensions.AnimatedValue +import com.tangem.tap.common.compose.extensions.toAnimatable + +@Composable +fun HorizontalSlidingImage( + painter: Painter, + paused: Boolean, + duration: Int, + itemSize: DpSize, + startOffset: Float, + targetOffset: Float, + contentDescription: String, +) { + val translateX = AnimatedValue(startOffset * -1f, (startOffset + targetOffset) * -1f) + + Image( + modifier = Modifier + .requiredWidth(itemSize.width) + .requiredHeight(itemSize.height) + .graphicsLayer( + translationX = translateX.toAnimatable(isPaused = paused, duration = duration).value + ), + alignment = Alignment.TopStart, + contentScale = ContentScale.FillBounds, + painter = painter, + contentDescription = contentDescription, + ) +} + +@Composable +fun StoriesTextAnimation( + slideInDuration: Int = 500, + slideInDelay: Int = 200, + slideDistance: Dp = 60.dp, + label: String = "", + content: @Composable (Modifier) -> Unit +) { + val isLaunched = remember { mutableStateOf(false) } + val transition = updateTransition(targetState = isLaunched.value, label = label) + + val offsetY = transition.animateDp( + transitionSpec = { + tween( + durationMillis = slideInDuration, + delayMillis = slideInDelay, + easing = FastOutSlowInEasing + ) + }, + label = "Slide in" + ) { value -> if (value) 0.dp else slideDistance } + + val alpha = transition.animateFloat( + transitionSpec = { + tween( + durationMillis = slideInDuration * 2, + delayMillis = slideInDelay, + easing = FastOutSlowInEasing + ) + }, + label = "Visibility" + ) { value -> if (value) 1f else 0f } + + content( + Modifier + .absoluteOffset(y = offsetY.value) + .alpha(alpha.value) + ) + + LaunchedEffect(Unit) { isLaunched.value = true } +} + +@Composable +fun StoriesBottomImageAnimation( + initialScale: Float = 2.5f, + firstStepDuration: Int, + totalDuration: Int, + content: @Composable (Modifier) -> Unit +) { + val scaleSwitchBarrier = 1.15f + val secondStepDuration = totalDuration - firstStepDuration + + val isFirstStepLaunched = remember { mutableStateOf(false) } + val isSecondStepLaunched = remember { mutableStateOf(false) } + + val firstTransition = updateTransition( + targetState = isFirstStepLaunched.value, + label = "Image appearing" + ) + val firstScaleStep = firstTransition.animateFloat( + transitionSpec = { + tween( + durationMillis = firstStepDuration, + easing = FastOutLinearInEasing, + ) + }, + 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( + durationMillis = secondStepDuration, + easing = LinearEasing, + ) + }, + label = "Outgoing scale" + ) { value -> if (value) 1f else scaleSwitchBarrier } + + 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) + } else { + Modifier.scale(secondScaleStep.value) + }.alpha(fadeIn.value) + + content(modifier) + + LaunchedEffect(Unit) { isFirstStepLaunched.value = true } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesGeneralContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesGeneralContent.kt deleted file mode 100644 index 410a8118aa..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesGeneralContent.kt +++ /dev/null @@ -1,162 +0,0 @@ -package com.tangem.tap.features.home.compose - -import android.content.Context -import android.graphics.Typeface -import android.util.TypedValue -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -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 androidx.compose.ui.viewinterop.AndroidView -import com.tangem.tangem_sdk_new.extensions.dpToPx -import com.tangem.tap.common.compose.SpacerS16 -import com.tangem.tap.common.compose.SpacerS24 -import com.tangem.tap.common.compose.extensions.toAndroidGraphicsColor -import com.tangem.wallet.R - -@Composable -fun StoriesGeneralContent( - titleText: String, - subtitleText: String, - imageSource: Int?, - isDarkBackground: Boolean, - subtitleTextId: Int? = null, - imageComposable: (() -> Unit)? = null, -) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = - Modifier - .fillMaxWidth() - .fillMaxHeight(), - ) { - - Text( - text = titleText, - fontSize = 32.sp, - fontWeight = FontWeight.SemiBold, - modifier = Modifier - .padding(start = 40.dp, end = 40.dp), - color = if (isDarkBackground) Color.White else Color(0xFF090E13), - textAlign = TextAlign.Center - ) - - SpacerS16() - - SubtitleText(subtitleText, subtitleTextId) - - SpacerS24() - - if (imageSource != null) { - Image( - painter = painterResource(id = imageSource), - contentDescription = null, - contentScale = if (isDarkBackground) ContentScale.Inside else ContentScale.FillWidth, - modifier = Modifier.fillMaxWidth() - ) - } - imageComposable?.invoke() - } -} - -@Composable -fun SubtitleText(subtitleText: String, subtitleTextId: Int?) { - val color = Color(0xFFA6AAAD) - - if (subtitleTextId == null) { - Text( - text = subtitleText, - fontSize = 20.sp, - fontWeight = FontWeight.Normal, - modifier = Modifier.padding(start = 40.dp, end = 40.dp), - color = color, - textAlign = TextAlign.Center - ) - } else { - HtmlText(subtitleTextId) { context -> - val padding = context.dpToPx(40f).toInt() - TextView(context).apply { - layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - setPadding(padding, 0, padding, 0) - textAlignment = View.TEXT_ALIGNMENT_CENTER - typeface = Typeface.DEFAULT - setTextSize(TypedValue.COMPLEX_UNIT_SP, 20f) - setTextColor(color.toAndroidGraphicsColor()) - } - } - } -} - -@Composable -fun StoriesRevolutionaryWallet() { - StoriesGeneralContent( - titleText = stringResource(id = R.string.story_awe_title), - subtitleText = stringResource(id = R.string.story_awe_description), - imageSource = R.drawable.revolutionary_wallet, - isDarkBackground = true - ) -} - -@Composable -fun StoriesUltraSecureBackup() { - StoriesGeneralContent( - titleText = stringResource(id = R.string.story_backup_title), - subtitleText = stringResource(id = R.string.story_backup_description), - imageSource = R.drawable.floating_cards, - subtitleTextId = R.string.story_backup_description, - isDarkBackground = false - ) -} - -@Composable -fun StoriesThousandsOfCurrencies() { - StoriesGeneralContent( - titleText = stringResource(id = R.string.story_currencies_title), - subtitleText = stringResource(id = R.string.story_currencies_description), - imageSource = R.drawable.thousands_of_currencies, - isDarkBackground = false - ) -} - -@Composable -fun StoriesWeb3() { - StoriesGeneralContent( - titleText = stringResource(id = R.string.story_web3_title), - subtitleText = stringResource(id = R.string.story_web3_description), - imageSource = R.drawable.web_3, - isDarkBackground = false - ) -} - -@Composable -fun StoriesWalletForEveryone() { - StoriesGeneralContent( - titleText = stringResource(id = R.string.story_finish_title), - subtitleText = stringResource(id = R.string.story_finish_description), - imageSource = R.drawable.wallet_for_everyone, - isDarkBackground = true - ) -} - -@Composable -fun HtmlText( - stringResId: Int, - modifier: Modifier = Modifier, - factory: (Context) -> TextView -) { - AndroidView(factory, modifier) { it.text = it.context.getText(stringResId) } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt new file mode 100644 index 0000000000..9ee80263c3 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt @@ -0,0 +1,263 @@ +package com.tangem.tap.features.home.compose.content + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.tangem.tap.common.compose.SpacerH16 +import com.tangem.tap.common.compose.SpacerH32 +import com.tangem.tap.features.home.compose.StoriesBottomImageAnimation +import com.tangem.tap.features.home.compose.StoriesTextAnimation +import com.tangem.wallet.R + +@Composable +fun StoriesRevolutionaryWallet(stepDuration: Int) { + SplitContent( + topContent = { + TopContent( + titleText = stringResource(id = R.string.story_awe_title), + subtitleText = stringResource(id = R.string.story_awe_description).annotated(), + isDarkBackground = true, + ) + }, + bottomContent = { + StoriesBottomImageAnimation( + totalDuration = stepDuration, + firstStepDuration = 300, + ) { modifier -> + StoriesImage( + modifier = modifier, + drawableResId = R.drawable.revolutionary_wallet, + isDarkBackground = true, + ) + } + } + ) +} + +@Composable +fun StoriesUltraSecureBackup(isPaused: Boolean, stepDuration: Int) { + val subtitleText = buildAnnotatedString { + append(stringResource(id = R.string.story_backup_description_1)) + append(" ") + withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { + append(stringResource(id = R.string.story_backup_description_2_bold)) + } + append(" ") + append(stringResource(id = R.string.story_backup_description_3)) + } + SplitContent( + topContent = { + TopContent( + titleText = stringResource(id = R.string.story_backup_title), + subtitleText = subtitleText, + isDarkBackground = false, + ) + }, + bottomContent = { + FloatingCardsContent(isPaused, stepDuration) + } + ) +} + +@Composable +fun StoriesCurrencies(isPaused: Boolean, stepDuration: Int) { + SplitContent( + topContent = { + TopContent( + titleText = stringResource(id = R.string.story_currencies_title), + subtitleText = stringResource(id = R.string.story_currencies_description).annotated(), + isDarkBackground = false, + ) + }, + bottomContent = { + StoriesCurrenciesContent(paused = isPaused, duration = stepDuration) + } + ) +} + +@Composable +fun StoriesWeb3(isPaused: Boolean, stepDuration: Int) { + SplitContent( + topContent = { + TopContent( + titleText = stringResource(id = R.string.story_web3_title), + subtitleText = stringResource(id = R.string.story_web3_description).annotated(), + isDarkBackground = false, + ) + }, + bottomContent = { + StoriesWeb3Content(paused = isPaused, duration = stepDuration) + } + ) +} + +@Composable +fun StoriesWalletForEveryone(stepDuration: Int) { + SplitContent( + topContent = { + TopContent( + titleText = stringResource(id = R.string.story_finish_title), + subtitleText = stringResource(id = R.string.story_finish_description).annotated(), + isDarkBackground = true, + ) + }, + bottomContent = { + StoriesBottomImageAnimation( + totalDuration = stepDuration, + firstStepDuration = 500, + ) { modifier -> + StoriesImage( + modifier = modifier, + drawableResId = R.drawable.wallet_for_everyone, + isDarkBackground = true, + ) + } + } + ) +} + +@Composable +private fun SplitContent( + topContent: @Composable () -> Unit, + bottomContent: @Composable () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Top + ) { + topContent() + bottomContent() + } +} + +@Composable +private fun TopContent( + titleText: String, + subtitleText: AnnotatedString, + isDarkBackground: Boolean, +) { + SpacerH32() + StoriesTitleText( + text = titleText, + isDarkBackground = isDarkBackground, + ) + SpacerH16() + StoriesSubtitleText( + subtitleText = subtitleText, + ) + SpacerH32() +} + +@Composable +private fun StoriesTitleText( + text: String, + isDarkBackground: Boolean, +) { + StoriesTextAnimation( + slideInDuration = 500, + slideInDelay = 150, + ) { modifier -> + Text( + modifier = modifier + .padding(start = 40.dp, end = 40.dp), + text = text, + fontSize = 32.sp, + fontWeight = FontWeight.SemiBold, + color = if (isDarkBackground) Color.White else Color(0xFF090E13), + textAlign = TextAlign.Center + ) + } +} + +@Composable +private fun StoriesSubtitleText( + subtitleText: AnnotatedString, +) { + val color = Color(0xFFA6AAAD) + + StoriesTextAnimation( + slideInDuration = 500, + slideInDelay = 400, + ) { modifier -> + Text( + modifier = modifier + .padding(start = 40.dp, end = 40.dp), + fontWeight = FontWeight.Normal, + text = subtitleText, + fontSize = 20.sp, + color = color, + textAlign = TextAlign.Center + ) + } +} + +@Composable +private fun StoriesImage( + modifier: Modifier = Modifier, + @DrawableRes drawableResId: Int, + isDarkBackground: Boolean, +) { + Image( + painter = painterResource(id = drawableResId), + contentDescription = null, + contentScale = if (isDarkBackground) ContentScale.Inside else ContentScale.FillWidth, + modifier = modifier.fillMaxWidth() + ) +} + +private fun String.annotated(): AnnotatedString { + val source = this + return buildAnnotatedString { append(source) } +} + + +@Preview +@Composable +private fun RevolutionaryWalletPreview() { + StoriesRevolutionaryWallet(6000) +} + +@Preview +@Composable +private fun UltraSecureBackupPreview() { + StoriesUltraSecureBackup(false, 6000) +} + +@Preview +@Composable +private fun CurrenciesPreview() { + StoriesCurrencies(false, 6000) +} + + +@Preview +@Composable +private fun Web3Preview() { + StoriesWeb3(false, 6000) +} + + +@Preview +@Composable +private fun WalletForEveryonePreview() { + StoriesWalletForEveryone(6000) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt new file mode 100644 index 0000000000..198e71d324 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt @@ -0,0 +1,144 @@ +package com.tangem.tap.features.home.compose.content + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +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.platform.LocalConfiguration +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.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.HorizontalSlidingImage +import com.tangem.wallet.R + +@Composable +fun StoriesCurrenciesContent( + paused: Boolean, + duration: Int, +) { + val currencyDrawableList = remember { + listOf( + R.drawable.currency0, + R.drawable.currency1, + R.drawable.currency2, + R.drawable.currency3, + R.drawable.currency4, + ) + } + + val screenWidth = LocalConfiguration.current.screenWidthDp.dp + val decreaseRate = remember { 1f / currencyDrawableList.size } + val designItemHeight = remember { 82.dp } + + LightenBox { + Column(modifier = Modifier.graphicsLayer(clip = false)) { + currencyDrawableList.forEachIndexed { index, drawableResId -> + val painter = painterResource(id = drawableResId) + val scaledItemSize = scaleToDesignSize(painter.dpSize(), designItemHeight = designItemHeight) + val itemOversizedScreenWidthBy = scaledItemSize.width - screenWidth + val moveItemToStartOfScreen = itemOversizedScreenWidthBy / 2 + + val chessOffset = if (index.isEven()) 0.dp else scaledItemSize.halfHeight() + val animateFrom = chessOffset - moveItemToStartOfScreen + val animateTo = 50.dp - (50.dp * index * decreaseRate) + + HorizontalSlidingImage( + paused = paused, + duration = duration, + painter = painter, + itemSize = scaledItemSize, + startOffset = animateFrom.toPx(), + targetOffset = animateTo.toPx(), + contentDescription = "Currency row", + ) + SpacerH(12.dp) + } + } + } +} + +@Composable +fun StoriesWeb3Content( + paused: Boolean, + duration: Int, +) { + val dappsItemList = remember { + listOf( + R.drawable.dapps0, + R.drawable.dapps1, + R.drawable.dapps2, + R.drawable.dapps3, + R.drawable.dapps4, + R.drawable.dapps5, + ) + } + val screenWidth = LocalConfiguration.current.screenWidthDp.dp + val decreaseRate = remember { 1f / dappsItemList.size } + val designItemHeight = 75.dp + + LightenBox { + Column(modifier = Modifier.graphicsLayer(clip = false)) { + dappsItemList.forEachIndexed { index, drawableResId -> + val painter = painterResource(id = drawableResId) + val scaledItemSize = scaleToDesignSize(painter.dpSize(), designItemHeight = designItemHeight) + val itemOversizedScreenWidthBy = scaledItemSize.width - screenWidth + val moveItemToStartOfScreen = itemOversizedScreenWidthBy / 2 + + val chessOffset = if (index.isEven()) 0.dp else scaledItemSize.width / 3 + val animateFrom = chessOffset - moveItemToStartOfScreen + val animateTo = 70.dp - (70.dp * index * decreaseRate) + + HorizontalSlidingImage( + paused = paused, + duration = duration, + painter = painter, + itemSize = scaledItemSize, + startOffset = animateFrom.toPx(), + targetOffset = animateTo.toPx(), + contentDescription = "Web3 row", + ) + } + } + + } +} + +@Composable +private fun LightenBox(content: @Composable () -> Unit) { + Box() { + content() + Box( + modifier = Modifier + .fillMaxSize() + .padding(top = 250.dp) + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color.White.copy(alpha = 0f), + Color.White.copy(alpha = 0.75f), + Color.White.copy(alpha = 0.95f), + Color.White + ) + ) + ) + ) {} + } +} + +private fun scaleToDesignSize(itemSize: DpSize, designItemHeight: Dp): DpSize { + val scaleRate = itemSize.height / designItemHeight + return itemSize / scaleRate +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt new file mode 100644 index 0000000000..393642f976 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt @@ -0,0 +1,163 @@ +package com.tangem.tap.features.home.compose.content + +import androidx.annotation.StringRes +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +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 +fun FirstStoriesContent( + isPaused: Boolean, duration: Int = 8_000, + hideContent: (Boolean) -> Unit +) { + val screenState = remember { mutableStateOf(StartingScreenState.INIT) } + val progress = remember { Animatable(0f) } + + LaunchedEffect(isPaused) { + if (isPaused) { + progress.stop() + } else { + progress.animateTo( + targetValue = 2f, + animationSpec = tween( + durationMillis = duration, + easing = LinearEasing + ) + ) + } + } + + when (progress.value) { + in 0f..0.2f -> screenState.value = StartingScreenState.INIT + in 0.2f..0.3f -> screenState.value = StartingScreenState.BUY + in 0.3f..0.4f -> screenState.value = StartingScreenState.STORE + in 0.4f..0.5f -> screenState.value = StartingScreenState.SEND + in 0.5f..0.6f -> screenState.value = StartingScreenState.PAY + in 0.6f..0.7f -> screenState.value = StartingScreenState.EXCHANGE + in 0.7f..0.8f -> screenState.value = StartingScreenState.BORROW + in 0.8f..1f -> screenState.value = StartingScreenState.LEND + in 1f..1.2f -> screenState.value = StartingScreenState.SHOW_CARD + in 1.2f..2f -> screenState.value = StartingScreenState.MEET_TANGEM + } + + if (screenState.value == StartingScreenState.INIT) hideContent(true) + if (screenState.value == StartingScreenState.BUY) hideContent(false) + + val style = TextStyle( + fontSize = 60.sp, + fontWeight = FontWeight.SemiBold, + color = Color.White, + textAlign = TextAlign.Center, + ) + val textId = screenState.textId() + + Box(modifier = Modifier.fillMaxSize()) { + + if (screenState.isSplashingTextDisplaying()) { + TextAutoSize( + modifier = Modifier + .align(Alignment.Center) + .padding(start = 20.dp, end = 20.dp, bottom = 100.dp), + text = textId?.let { stringResource(textId) } ?: "", + textStyle = style, + fontSizeRange = FontSizeRange(20.sp, 60.sp) + ) + } else { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box( + modifier = Modifier + .weight(0.7f) + ) { + if (screenState.isMeetTangemDisplaying()) { + StoriesTextAnimation( + slideInDelay = 0, + ) { modifier -> + TextAutoSize( + modifier = modifier + .padding(start = 20.dp, end = 20.dp, top = 50.dp) + .alpha(if (screenState.value == StartingScreenState.SHOW_CARD) 0f else 1f), + text = textId?.let { stringResource(textId) } ?: "", + textStyle = style, + fontSizeRange = FontSizeRange(30.sp, 50.sp) + ) + } + + } + } + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1.2f) + .wrapContentSize(), + ) { + StoriesBottomImageAnimation( + totalDuration = duration, + firstStepDuration = 400, + ) { modifier -> + Image( + modifier = modifier.fillMaxWidth(), + painter = painterResource(id = R.drawable.meet_tangem), + contentDescription = "Tangem Wallet card", + ) + } + } + } + } + } +} + +private enum class StartingScreenState { + INIT, BUY, STORE, SEND, PAY, EXCHANGE, BORROW, LEND, SHOW_CARD, MEET_TANGEM +} + +@StringRes +private fun MutableState.textId(): Int? = when (this.value) { + StartingScreenState.INIT -> null + StartingScreenState.BUY -> R.string.story_meet_buy + StartingScreenState.STORE -> R.string.story_meet_store + StartingScreenState.SEND -> R.string.story_meet_send + StartingScreenState.PAY -> R.string.story_meet_pay + StartingScreenState.EXCHANGE -> R.string.story_meet_exchange + StartingScreenState.BORROW -> R.string.story_meet_borrow + StartingScreenState.LEND -> R.string.story_meet_lend + StartingScreenState.SHOW_CARD -> R.string.story_meet_title + StartingScreenState.MEET_TANGEM -> R.string.story_meet_title +} + +private fun MutableState.isSplashingTextDisplaying(): Boolean { + return this.value != StartingScreenState.MEET_TANGEM && + this.value != StartingScreenState.SHOW_CARD +} + +private fun MutableState.isMeetTangemDisplaying(): Boolean { + return this.value == StartingScreenState.MEET_TANGEM +} + + +@Preview +@Composable +fun FirstStoriesPreview() { + FirstStoriesContent(false, 8000) {} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt new file mode 100644 index 0000000000..8528982857 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt @@ -0,0 +1,103 @@ +package com.tangem.tap.features.home.compose.content + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +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.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 + +/** +[REDACTED_AUTHOR] + */ +@Composable +fun FloatingCardsContent( + isPaused: Boolean, + stepDuration: Int, +) { + + val imageBitmap = asImageBitmap(R.drawable.card_placeholder_wallet) + val cards = listOf( + FloatingCard.first(), + FloatingCard.second(), + FloatingCard.third(), + ) + Box() { + cards.forEach { floatingCard -> + FloatingCard.Item( + isPaused = isPaused, + imageBitmap = imageBitmap, + cardValues = floatingCard, + stepDuration = stepDuration, + ) + } + } +} + +private data class CardValues( + val translateX: AnimatedValue = AnimatedValue(0f, 0f), + val translateY: AnimatedValue = AnimatedValue(0f, 0f), + val rotationX: AnimatedValue = AnimatedValue(0f, 0f), + val rotationY: AnimatedValue = AnimatedValue(0f, 0f), + val rotationZ: AnimatedValue = AnimatedValue(0f, 0f), + val scale: AnimatedValue = AnimatedValue(1f, 1f), +) + +private class FloatingCard { + companion object { + + @Composable + fun Item( + isPaused: Boolean, + stepDuration: Int, + imageBitmap: ImageBitmap, + cardValues: CardValues, + ) { + Image( + bitmap = imageBitmap, + contentDescription = "Floating Tangem card", + modifier = Modifier + .graphicsLayer( + translationX = cardValues.translateX.toAnimatable(isPaused, stepDuration).value, + translationY = cardValues.translateY.toAnimatable(isPaused, stepDuration).value, + rotationX = cardValues.rotationX.toAnimatable(isPaused, stepDuration).value, + rotationY = cardValues.rotationY.toAnimatable(isPaused, stepDuration).value, + rotationZ = cardValues.rotationZ.toAnimatable(isPaused, stepDuration).value, + scaleX = cardValues.scale.toAnimatable(isPaused, stepDuration).value, + scaleY = cardValues.scale.toAnimatable(isPaused, stepDuration).value, + ), + ) + } + + fun first(): CardValues = CardValues( + translateX = -400f to -350f, + translateY = 30f to 32f, + rotationX = 10f to 15f, + rotationY = 15f to 15f, + rotationZ = 40f to 27f, + scale = 0.6f to 0.6f, + ) + + fun second(): CardValues = CardValues( + translateX = 350f to 300f, + translateY = -70f to 0f, + rotationX = 30f to 48f, + rotationY = 0f to 5f, + rotationZ = -34f to -42f, + scale = 0.47f to 0.35f, + ) + + fun third(): CardValues = CardValues( + translateX = 320f to 250f, + translateY = 500f to 500f, + rotationX = 0f to 3f, + rotationY = 10f to 10f, + rotationZ = -45f to -30f, + scale = 0.6f to 0.75f, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt similarity index 97% rename from app/src/main/java/com/tangem/tap/features/home/compose/HomeButtons.kt rename to app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt index 04fb1f2647..d49013db87 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.features.home.compose +package com.tangem.tap.features.home.compose.views import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesProgressBar.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt similarity index 97% rename from app/src/main/java/com/tangem/tap/features/home/compose/StoriesProgressBar.kt rename to app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt index d121949529..096b87cb0b 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesProgressBar.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.features.home.compose +package com.tangem.tap.features.home.compose.views import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.LinearEasing diff --git a/app/src/main/res/drawable/currency0.png b/app/src/main/res/drawable/currency0.png new file mode 100644 index 0000000000..0d293ec8bb Binary files /dev/null and b/app/src/main/res/drawable/currency0.png differ diff --git a/app/src/main/res/drawable/currency1.png b/app/src/main/res/drawable/currency1.png new file mode 100644 index 0000000000..37144efffe Binary files /dev/null and b/app/src/main/res/drawable/currency1.png differ diff --git a/app/src/main/res/drawable/currency2.png b/app/src/main/res/drawable/currency2.png new file mode 100644 index 0000000000..bd75790221 Binary files /dev/null and b/app/src/main/res/drawable/currency2.png differ diff --git a/app/src/main/res/drawable/currency3.png b/app/src/main/res/drawable/currency3.png new file mode 100644 index 0000000000..c517578807 Binary files /dev/null and b/app/src/main/res/drawable/currency3.png differ diff --git a/app/src/main/res/drawable/currency4.png b/app/src/main/res/drawable/currency4.png new file mode 100644 index 0000000000..9a39d5651d Binary files /dev/null and b/app/src/main/res/drawable/currency4.png differ diff --git a/app/src/main/res/drawable/dapps0.png b/app/src/main/res/drawable/dapps0.png new file mode 100644 index 0000000000..6152b1215b Binary files /dev/null and b/app/src/main/res/drawable/dapps0.png differ diff --git a/app/src/main/res/drawable/dapps1.png b/app/src/main/res/drawable/dapps1.png new file mode 100644 index 0000000000..2d4effaf8b Binary files /dev/null and b/app/src/main/res/drawable/dapps1.png differ diff --git a/app/src/main/res/drawable/dapps2.png b/app/src/main/res/drawable/dapps2.png new file mode 100644 index 0000000000..f7643c4ec7 Binary files /dev/null and b/app/src/main/res/drawable/dapps2.png differ diff --git a/app/src/main/res/drawable/dapps3.png b/app/src/main/res/drawable/dapps3.png new file mode 100644 index 0000000000..9e0110179b Binary files /dev/null and b/app/src/main/res/drawable/dapps3.png differ diff --git a/app/src/main/res/drawable/dapps4.png b/app/src/main/res/drawable/dapps4.png new file mode 100644 index 0000000000..4eae3c0985 Binary files /dev/null and b/app/src/main/res/drawable/dapps4.png differ diff --git a/app/src/main/res/drawable/dapps5.png b/app/src/main/res/drawable/dapps5.png new file mode 100644 index 0000000000..0c581624c7 Binary files /dev/null and b/app/src/main/res/drawable/dapps5.png differ diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index 7dd40e2633..a9a31d3866 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -23,7 +23,9 @@ Revolutionary Hardware Wallet Store your crypto assets secure while keeping private keys contained in your card Ultra Secure Backup - Up to 3 physical cards to one wallet + Up to + 3 physical cards + to one wallet Thousands of Currencies A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card DeFi Compatible diff --git a/app/src/main/res/values-fr/strings_final.xml b/app/src/main/res/values-fr/strings_final.xml index 7fb2245b89..d34f57338a 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -23,7 +23,9 @@ Revolutionary Hardware Wallet Store your crypto assets secure while keeping private keys contained in your card Ultra Secure Backup - Up to 3 physical cards to one wallet + Up to + 3 physical cards + to one wallet Thousands of Currencies A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card DeFi Compatible diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml index d561bcdb56..1a7d8051eb 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -23,7 +23,9 @@ Revolutionary Hardware Wallet Store your crypto assets secure while keeping private keys contained in your card Ultra Secure Backup - Up to 3 physical cards to one wallet + Up to + 3 physical cards + to one wallet Thousands of Currencies A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card DeFi Compatible diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml index 496f4f7815..4359a84d9e 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -23,7 +23,9 @@ Революционный аппаратный кошелек Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. Все ключи в безопасности - До трех карт с одним кошельком + До + трех карт + с одним кошельком Тысячи криптовалют Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте Поддержка DeFi diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index 75bcce3b9a..c59bf4b50a 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -23,7 +23,9 @@ Revolutionary Hardware Wallet Store your crypto assets secure while keeping private keys contained in your card Ultra Secure Backup - Up to 3 physical cards to one wallet + Up to + 3 physical cards + to one wallet Thousands of Currencies A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card DeFi Compatible