Updated on 2026-08-14
This commit is contained in:
parent
25bae0ed98
commit
e2b5e5b07b
12 changed files with 539 additions and 153 deletions
|
|
@ -27,7 +27,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
||||||
|
|
||||||
override val stack: Value<ChildStack<AppRoute, Child>> = childStack(
|
override val stack: Value<ChildStack<AppRoute, Child>> = childStack(
|
||||||
source = navigationProvider.getOrCreateTyped(),
|
source = navigationProvider.getOrCreateTyped(),
|
||||||
serializer = AppRoute.serializer(),
|
serializer = AppRoute.serializer(), // TODO Maybe set this to null for AppRoute.Onboarding case. Need to check
|
||||||
initialConfiguration = getInitialRoute(),
|
initialConfiguration = getInitialRoute(),
|
||||||
handleBackButton = false,
|
handleBackButton = false,
|
||||||
childFactory = ::child,
|
childFactory = ::child,
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDefaultFallbackUrl(): String = Artwork.DEFAULT_IMG_URL
|
||||||
|
|
||||||
private fun getFallbackArtworkUrl(cardId: String): String {
|
private fun getFallbackArtworkUrl(cardId: String): String {
|
||||||
return when {
|
return when {
|
||||||
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
|
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
|
||||||
|
|
@ -45,7 +47,7 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardV
|
||||||
else -> when (TwinsHelper.getTwinCardNumber(cardId)) {
|
else -> when (TwinsHelper.getTwinCardNumber(cardId)) {
|
||||||
TwinCardNumber.First -> Artwork.TWIN_CARD_1_URL
|
TwinCardNumber.First -> Artwork.TWIN_CARD_1_URL
|
||||||
TwinCardNumber.Second -> Artwork.TWIN_CARD_2_URL
|
TwinCardNumber.Second -> Artwork.TWIN_CARD_2_URL
|
||||||
else -> Artwork.DEFAULT_IMG_URL
|
else -> getDefaultFallbackUrl()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ dependencies {
|
||||||
implementation(projects.domain.feedback)
|
implementation(projects.domain.feedback)
|
||||||
implementation(projects.domain.core)
|
implementation(projects.domain.core)
|
||||||
implementation(projects.domain.card)
|
implementation(projects.domain.card)
|
||||||
|
implementation(projects.domain.wallets)
|
||||||
|
|
||||||
/** Tangem libraries */
|
/** Tangem libraries */
|
||||||
implementation(projects.libs.tangemSdkApi)
|
implementation(projects.libs.tangemSdkApi)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.onboarding.v2.multiwallet.impl
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children
|
import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children
|
||||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide
|
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide
|
||||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation
|
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation
|
||||||
|
|
@ -21,6 +22,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWall
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.MultiWalletCreateWalletComponent
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.MultiWalletCreateWalletComponent
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel
|
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
|
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.OnboardingMultiWallet
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
|
@ -95,13 +97,20 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content(modifier: Modifier) {
|
override fun Content(modifier: Modifier) {
|
||||||
val stackState by childStack.subscribeAsState()
|
val stackState by childStack.subscribeAsState()
|
||||||
|
val state by model.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
Children(
|
OnboardingMultiWallet(
|
||||||
stack = stackState,
|
state = state,
|
||||||
animation = stackAnimation(slide()),
|
modifier = modifier,
|
||||||
) {
|
childContent = { mdfr ->
|
||||||
it.instance.Content(Modifier)
|
Children(
|
||||||
}
|
stack = stackState,
|
||||||
|
animation = stackAnimation(slide()),
|
||||||
|
) {
|
||||||
|
it.instance.Content(mdfr)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui
|
package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
@ -26,22 +22,8 @@ fun MultiWalletBackup(state: MultiWalletBackupUM, modifier: Modifier = Modifier)
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.navigationBarsPadding()
|
.navigationBarsPadding(),
|
||||||
.verticalScroll(rememberScrollState()),
|
|
||||||
) {
|
) {
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.weight(1f),
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.align(Alignment.Center)
|
|
||||||
.size(100.dp)
|
|
||||||
.background(Color.Black),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
when (state) {
|
when (state) {
|
||||||
is MultiWalletBackupUM.Wallet1 -> Wallet1Content(state)
|
is MultiWalletBackupUM.Wallet1 -> Wallet1Content(state)
|
||||||
is MultiWalletBackupUM.Wallet2 -> TODO()
|
is MultiWalletBackupUM.Wallet2 -> TODO()
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Wallet1Artwork(modifier: Modifier = Modifier) {
|
|
||||||
Box(modifier) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun Preview() {
|
|
||||||
TangemThemePreview {
|
|
||||||
Wallet1Artwork()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +1,8 @@
|
||||||
package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui
|
package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -42,22 +37,9 @@ fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: Modifier
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.navigationBarsPadding()
|
.navigationBarsPadding(),
|
||||||
.verticalScroll(rememberScrollState()),
|
verticalArrangement = Arrangement.Bottom,
|
||||||
) {
|
) {
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.weight(1f),
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.align(Alignment.Center)
|
|
||||||
.size(100.dp)
|
|
||||||
.background(Color.Black),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
PrimaryButtonIconEnd(
|
PrimaryButtonIconEnd(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp)
|
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package com.tangem.features.onboarding.v2.multiwallet.impl.model
|
|
||||||
|
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
|
||||||
import com.tangem.features.onboarding.v2.impl.R
|
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM
|
|
||||||
|
|
||||||
internal fun resetCardDialog(onConfirm: () -> Unit, onDismiss: () -> Unit, onDismissButtonClick: () -> Unit) =
|
|
||||||
OnboardingMultiWalletUM.Dialog(
|
|
||||||
title = resourceReference(R.string.onboarding_activation_error_title),
|
|
||||||
description = resourceReference(R.string.onboarding_activation_error_message),
|
|
||||||
confirmButtonText = resourceReference(R.string.common_ok),
|
|
||||||
dismissButtonText = resourceReference(R.string.common_support),
|
|
||||||
onDismiss = onDismiss,
|
|
||||||
onConfirm = onConfirm,
|
|
||||||
onDismissButtonClick = onDismissButtonClick,
|
|
||||||
)
|
|
||||||
|
|
@ -4,11 +4,11 @@ import com.tangem.core.decompose.di.ComponentScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.core.decompose.model.ParamsContainer
|
import com.tangem.core.decompose.model.ParamsContainer
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
|
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
|
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -17,8 +17,9 @@ internal class OnboardingMultiWalletModel @Inject constructor(
|
||||||
paramsContainer: ParamsContainer,
|
paramsContainer: ParamsContainer,
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private val params = paramsContainer.require<OnboardingMultiWalletComponent.Params>()
|
private val params = paramsContainer.require<OnboardingMultiWalletComponent.Params>()
|
||||||
|
private val getCardImageUseCase = GetCardImageUseCase()
|
||||||
|
private val _uiState = MutableStateFlow(OnboardingMultiWalletUM())
|
||||||
|
|
||||||
val state = MutableStateFlow(
|
val state = MutableStateFlow(
|
||||||
OnboardingMultiWalletState(
|
OnboardingMultiWalletState(
|
||||||
|
|
@ -27,12 +28,15 @@ internal class OnboardingMultiWalletModel @Inject constructor(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val uiState = _uiState.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// TODO add analytics
|
// TODO add analytics
|
||||||
// if (!manager.isActivationStarted(notNullCard.cardId)) {
|
// if (!manager.isActivationStarted(notNullCard.cardId)) {
|
||||||
// Analytics.send(Onboarding.Started())
|
// Analytics.send(Onboarding.Started())
|
||||||
// }
|
// }
|
||||||
initScreenTitleSub()
|
initScreenTitleSub()
|
||||||
|
loadCardArtwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getInitialStep(): OnboardingMultiWalletState.Step {
|
private fun getInitialStep(): OnboardingMultiWalletState.Step {
|
||||||
|
|
@ -60,4 +64,17 @@ internal class OnboardingMultiWalletModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadCardArtwork() {
|
||||||
|
modelScope.launch {
|
||||||
|
val artwork =
|
||||||
|
getCardImageUseCase.invoke(params.scanResponse.card.cardId, params.scanResponse.card.cardPublicKey)
|
||||||
|
|
||||||
|
if (artwork != getCardImageUseCase.getDefaultFallbackUrl()) {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(artworkUrl = artwork)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,79 +1,63 @@
|
||||||
package com.tangem.features.onboarding.v2.multiwallet.impl.ui
|
package com.tangem.features.onboarding.v2.multiwallet.impl.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.core.ui.components.BasicDialog
|
|
||||||
import com.tangem.core.ui.components.DialogButtonUM
|
|
||||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
|
||||||
import com.tangem.core.ui.components.SecondaryButton
|
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.features.onboarding.v2.impl.R
|
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM
|
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun OnboardingMultiWallet(state: OnboardingMultiWalletUM, modifier: Modifier = Modifier) {
|
@Suppress("MagicNumber")
|
||||||
if (state.dialog != null) {
|
internal fun OnboardingMultiWallet(
|
||||||
BasicDialog(
|
state: OnboardingMultiWalletUM,
|
||||||
title = state.dialog.title.resolveReference(),
|
modifier: Modifier = Modifier,
|
||||||
message = state.dialog.description.resolveReference(),
|
childContent: @Composable (Modifier) -> Unit = {},
|
||||||
confirmButton = DialogButtonUM(
|
) {
|
||||||
title = state.dialog.confirmButtonText.resolveReference(),
|
|
||||||
onClick = state.dialog.onConfirm,
|
|
||||||
),
|
|
||||||
dismissButton = DialogButtonUM(
|
|
||||||
title = state.dialog.dismissButtonText.resolveReference(),
|
|
||||||
onClick = state.dialog.onDismissButtonClick,
|
|
||||||
),
|
|
||||||
onDismissDialog = state.dialog.onDismiss,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.navigationBarsPadding()
|
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
Box(Modifier.weight(1f))
|
WalletArtworks(
|
||||||
|
url = state.artworkUrl,
|
||||||
PrimaryButtonIconEnd(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp)
|
.weight(.56f)
|
||||||
|
.padding(horizontal = 34.dp)
|
||||||
|
.padding(top = 20.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
iconResId = R.drawable.ic_tangem_24,
|
state = WalletArtworksState.Folded,
|
||||||
text = stringResource(R.string.onboarding_create_wallet_button_create_wallet),
|
|
||||||
onClick = state.onCreateWalletClick,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
SecondaryButton(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(.44f),
|
||||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
contentAlignment = Alignment.BottomStart,
|
||||||
.fillMaxWidth(),
|
) {
|
||||||
text = stringResource(R.string.onboarding_create_wallet_options_button_options),
|
childContent(Modifier)
|
||||||
onClick = state.onOtherOptionsClick,
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview(showBackground = true, widthDp = 360, heightDp = 640)
|
||||||
@Composable
|
@Composable
|
||||||
private fun Preview() {
|
private fun Preview() {
|
||||||
TangemThemePreview {
|
TangemThemePreview {
|
||||||
OnboardingMultiWallet(
|
OnboardingMultiWallet(
|
||||||
state = OnboardingMultiWalletUM(
|
state = OnboardingMultiWalletUM(artworkUrl = null),
|
||||||
onCreateWalletClick = {},
|
childContent = { md ->
|
||||||
showSeedPhraseOption = true,
|
Box(
|
||||||
onBack = {},
|
modifier = md
|
||||||
onOtherOptionsClick = {},
|
.fillMaxWidth()
|
||||||
dialog = null,
|
.height(200.dp)
|
||||||
),
|
.background(Color.DarkGray),
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,462 @@
|
||||||
|
@file:Suppress("MagicNumber", "UnnecessaryParentheses", "CyclomaticComplexMethod", "LongMethod")
|
||||||
|
package com.tangem.features.onboarding.v2.multiwallet.impl.ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.Transition
|
||||||
|
import androidx.compose.animation.core.animateFloat
|
||||||
|
import androidx.compose.animation.core.updateTransition
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.util.fastForEach
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.features.onboarding.v2.impl.R
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
sealed class WalletArtworksState {
|
||||||
|
data object Folded : WalletArtworksState()
|
||||||
|
|
||||||
|
data object Fan : WalletArtworksState()
|
||||||
|
|
||||||
|
data class Stack(
|
||||||
|
val threeCards: Boolean = true,
|
||||||
|
) : WalletArtworksState()
|
||||||
|
|
||||||
|
data class Unfold(
|
||||||
|
val step: Step = Step.First,
|
||||||
|
) : WalletArtworksState() {
|
||||||
|
enum class Step {
|
||||||
|
First, Second, Third
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Leapfrog(
|
||||||
|
val threeCards: Boolean = true,
|
||||||
|
val step: Step = Step.Second,
|
||||||
|
) : WalletArtworksState() {
|
||||||
|
enum class Step {
|
||||||
|
Second, Third
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WalletArtworks(url: String?, state: WalletArtworksState, modifier: Modifier = Modifier) {
|
||||||
|
BoxWithConstraints(
|
||||||
|
modifier
|
||||||
|
.heightIn(min = 180.dp)
|
||||||
|
.widthIn(max = 450.dp),
|
||||||
|
) {
|
||||||
|
val animationWidth = maxWidth
|
||||||
|
val density = LocalDensity.current.density
|
||||||
|
|
||||||
|
var animationState by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
state.toTransitionSetState(
|
||||||
|
maxWidthDp = animationWidth.value,
|
||||||
|
density = density,
|
||||||
|
maxHeightDp = maxHeight.value,
|
||||||
|
).first(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val transition1 = updateTransition(animationState.walletCard1, label = "transition")
|
||||||
|
val transition2 = updateTransition(animationState.walletCard2, label = "transition")
|
||||||
|
val transition3 = updateTransition(animationState.walletCard3, label = "transition")
|
||||||
|
|
||||||
|
LaunchedEffect(state) {
|
||||||
|
val animationSetState = state.toTransitionSetState(
|
||||||
|
maxWidthDp = animationWidth.value,
|
||||||
|
density = density,
|
||||||
|
maxHeightDp = maxHeight.value,
|
||||||
|
)
|
||||||
|
animationSetState.fastForEach {
|
||||||
|
animationState = it
|
||||||
|
val maxTime = maxOf(
|
||||||
|
transition1.totalDurationNanos,
|
||||||
|
transition2.totalDurationNanos,
|
||||||
|
transition3.totalDurationNanos,
|
||||||
|
)
|
||||||
|
delay(TimeUnit.NANOSECONDS.toMillis(maxTime))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedWalletCards(
|
||||||
|
url = url,
|
||||||
|
modifier = Modifier
|
||||||
|
.widthIn(max = 450.dp)
|
||||||
|
.matchParentSize(),
|
||||||
|
transition1 = transition1,
|
||||||
|
transition2 = transition2,
|
||||||
|
transition3 = transition3,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class CardsTransitionState(
|
||||||
|
val walletCard1: WalletCardTransitionState,
|
||||||
|
val walletCard2: WalletCardTransitionState,
|
||||||
|
val walletCard3: WalletCardTransitionState,
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class WalletCardTransitionState(
|
||||||
|
val alpha: Float = 1f,
|
||||||
|
val xTranslation: Float = 0f,
|
||||||
|
val yTranslation: Float = 0f,
|
||||||
|
val xScale: Float = 1f,
|
||||||
|
val yScale: Float = 1f,
|
||||||
|
val rotation: Float = 0f,
|
||||||
|
val zIndex: Float = 0f,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AnimatedWalletCards(
|
||||||
|
url: String?,
|
||||||
|
transition1: Transition<WalletCardTransitionState>,
|
||||||
|
transition2: Transition<WalletCardTransitionState>,
|
||||||
|
transition3: Transition<WalletCardTransitionState>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val alpha1 by transition1.animateFloat(label = "alpha1") { it.alpha }
|
||||||
|
val translationX1 by transition1.animateFloat(label = "translationX1") { it.xTranslation }
|
||||||
|
val translationY1 by transition1.animateFloat(label = "translationY1") { it.yTranslation }
|
||||||
|
val rotation1 by transition1.animateFloat(label = "rotation1") { it.rotation }
|
||||||
|
val scaleX1 by transition1.animateFloat(label = "scaleX1") { it.xScale }
|
||||||
|
val scaleY1 by transition1.animateFloat(label = "scaleY1") { it.yScale }
|
||||||
|
val zIndex1 = transition1.currentState.zIndex
|
||||||
|
|
||||||
|
val alpha2 by transition2.animateFloat(label = "alpha2") { it.alpha }
|
||||||
|
val translationX2 by transition2.animateFloat(label = "translationX2") { it.xTranslation }
|
||||||
|
val translationY2 by transition2.animateFloat(label = "translationY2") { it.yTranslation }
|
||||||
|
val rotation2 by transition2.animateFloat(label = "rotation2") { it.rotation }
|
||||||
|
val scaleX2 by transition2.animateFloat(label = "scaleX2") { it.xScale }
|
||||||
|
val scaleY2 by transition2.animateFloat(label = "scaleY2") { it.yScale }
|
||||||
|
val zIndex2 = transition2.currentState.zIndex
|
||||||
|
|
||||||
|
val alpha3 by transition3.animateFloat(label = "alpha3") { it.alpha }
|
||||||
|
val translationX3 by transition3.animateFloat(label = "translationX3") { it.xTranslation }
|
||||||
|
val translationY3 by transition3.animateFloat(label = "translationY3") { it.yTranslation }
|
||||||
|
val rotation3 by transition3.animateFloat(label = "rotation3") { it.rotation }
|
||||||
|
val scaleX3 by transition3.animateFloat(label = "scaleX3") { it.xScale }
|
||||||
|
val scaleY3 by transition3.animateFloat(label = "scaleY3") { it.yScale }
|
||||||
|
val zIndex3 = transition3.targetState.zIndex
|
||||||
|
|
||||||
|
Box(modifier) {
|
||||||
|
WalletCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.zIndex(zIndex3)
|
||||||
|
.matchParentSize()
|
||||||
|
.graphicsLayer {
|
||||||
|
translationX = translationX3
|
||||||
|
translationY = translationY3
|
||||||
|
}
|
||||||
|
.graphicsLayer {
|
||||||
|
alpha = alpha3
|
||||||
|
scaleX = scaleX3
|
||||||
|
scaleY = scaleY3
|
||||||
|
rotationZ = rotation3
|
||||||
|
},
|
||||||
|
url = url,
|
||||||
|
)
|
||||||
|
|
||||||
|
WalletCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.zIndex(zIndex2)
|
||||||
|
.matchParentSize()
|
||||||
|
.graphicsLayer {
|
||||||
|
translationX = translationX2
|
||||||
|
translationY = translationY2
|
||||||
|
}
|
||||||
|
.graphicsLayer {
|
||||||
|
alpha = alpha2
|
||||||
|
rotationZ = rotation2
|
||||||
|
|
||||||
|
scaleX = scaleX2
|
||||||
|
scaleY = scaleY2
|
||||||
|
},
|
||||||
|
url = url,
|
||||||
|
)
|
||||||
|
|
||||||
|
WalletCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.zIndex(zIndex1)
|
||||||
|
.matchParentSize()
|
||||||
|
.graphicsLayer {
|
||||||
|
scaleX = scaleX1
|
||||||
|
scaleY = scaleY1
|
||||||
|
}
|
||||||
|
.graphicsLayer {
|
||||||
|
alpha = alpha1
|
||||||
|
translationX = translationX1
|
||||||
|
translationY = translationY1
|
||||||
|
rotationZ = rotation1
|
||||||
|
},
|
||||||
|
url = url,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun WalletCard(url: String?, modifier: Modifier = Modifier) {
|
||||||
|
AsyncImage(
|
||||||
|
modifier = modifier,
|
||||||
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(url)
|
||||||
|
.crossfade(true)
|
||||||
|
.build(),
|
||||||
|
placeholder = painterResource(R.drawable.card_placeholder_black),
|
||||||
|
error = painterResource(R.drawable.card_placeholder_black),
|
||||||
|
fallback = painterResource(R.drawable.card_placeholder_black),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun WalletArtworksState.toTransitionSetState(maxWidthDp: Float, maxHeightDp: Float, density: Float) =
|
||||||
|
when (this) {
|
||||||
|
is WalletArtworksState.Folded -> listOf(
|
||||||
|
CardsTransitionState(
|
||||||
|
WalletCardTransitionState(),
|
||||||
|
WalletCardTransitionState(),
|
||||||
|
WalletCardTransitionState(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
is WalletArtworksState.Stack -> listOf(
|
||||||
|
CardsTransitionState(
|
||||||
|
walletCard1 = WalletCardTransitionState(),
|
||||||
|
walletCard2 = WalletCardTransitionState(),
|
||||||
|
walletCard3 = WalletCardTransitionState(),
|
||||||
|
),
|
||||||
|
CardsTransitionState(
|
||||||
|
walletCard1 = WalletCardTransitionState().copy(
|
||||||
|
alpha = 1f,
|
||||||
|
xTranslation = 0f,
|
||||||
|
yTranslation = 0f,
|
||||||
|
rotation = 0f,
|
||||||
|
xScale = 1f,
|
||||||
|
yScale = 1f,
|
||||||
|
),
|
||||||
|
walletCard2 = WalletCardTransitionState().copy(
|
||||||
|
alpha = 0.65f,
|
||||||
|
xTranslation = 0f,
|
||||||
|
yTranslation = ((maxHeightDp * (1 - 0.83f)) / 4 + 12) * density,
|
||||||
|
rotation = 0f,
|
||||||
|
xScale = 0.83f,
|
||||||
|
yScale = 0.83f,
|
||||||
|
),
|
||||||
|
walletCard3 = WalletCardTransitionState().copy(
|
||||||
|
alpha = if (threeCards) 0.55f else 0f,
|
||||||
|
xTranslation = 0f,
|
||||||
|
yTranslation = (maxHeightDp * (1 - 0.72f) / 4 + 24) * density,
|
||||||
|
rotation = 0f,
|
||||||
|
xScale = 0.72f,
|
||||||
|
yScale = 0.72f,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
is WalletArtworksState.Unfold -> listOfNotNull(
|
||||||
|
CardsTransitionState(
|
||||||
|
walletCard1 = WalletCardTransitionState(),
|
||||||
|
walletCard2 = WalletCardTransitionState().copy(
|
||||||
|
alpha = 0.65f,
|
||||||
|
),
|
||||||
|
walletCard3 = WalletCardTransitionState().copy(
|
||||||
|
alpha = 0.55f,
|
||||||
|
),
|
||||||
|
).takeIf { step == WalletArtworksState.Unfold.Step.First },
|
||||||
|
CardsTransitionState(
|
||||||
|
walletCard1 = WalletCardTransitionState().copy(
|
||||||
|
rotation = 5f,
|
||||||
|
),
|
||||||
|
walletCard2 = WalletCardTransitionState().copy(
|
||||||
|
alpha = when (step) {
|
||||||
|
WalletArtworksState.Unfold.Step.First -> 0.65f
|
||||||
|
WalletArtworksState.Unfold.Step.Second -> 1f
|
||||||
|
WalletArtworksState.Unfold.Step.Third -> 1f
|
||||||
|
},
|
||||||
|
rotation = -6f,
|
||||||
|
xTranslation = 0f,
|
||||||
|
yTranslation = -(maxHeightDp * (1 - 0.88f) / 4) * density,
|
||||||
|
xScale = 0.88f,
|
||||||
|
yScale = 0.88f,
|
||||||
|
),
|
||||||
|
walletCard3 = WalletCardTransitionState().copy(
|
||||||
|
alpha = when (step) {
|
||||||
|
WalletArtworksState.Unfold.Step.First -> 0.55f
|
||||||
|
WalletArtworksState.Unfold.Step.Second -> 0.55f
|
||||||
|
WalletArtworksState.Unfold.Step.Third -> 1f
|
||||||
|
},
|
||||||
|
rotation = -16f,
|
||||||
|
xTranslation = 0f,
|
||||||
|
yTranslation = -(maxHeightDp * (1 - 0.81f) / 4 + 2) * density,
|
||||||
|
xScale = 0.81f,
|
||||||
|
yScale = 0.81f,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
WalletArtworksState.Fan -> {
|
||||||
|
listOf(
|
||||||
|
CardsTransitionState(
|
||||||
|
walletCard1 = WalletCardTransitionState().copy(
|
||||||
|
xTranslation = maxWidthDp * 0.4f * density,
|
||||||
|
yTranslation = 35 * density,
|
||||||
|
rotation = 75f,
|
||||||
|
xScale = .85f,
|
||||||
|
yScale = .85f,
|
||||||
|
),
|
||||||
|
walletCard2 = WalletCardTransitionState().copy(
|
||||||
|
xTranslation = maxWidthDp * -0.4f * density,
|
||||||
|
yTranslation = -5 * density,
|
||||||
|
rotation = 75f,
|
||||||
|
xScale = .85f,
|
||||||
|
yScale = .85f,
|
||||||
|
),
|
||||||
|
walletCard3 = WalletCardTransitionState().copy(
|
||||||
|
rotation = 105f,
|
||||||
|
xScale = .85f,
|
||||||
|
yScale = .85f,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is WalletArtworksState.Leapfrog -> {
|
||||||
|
val translationY = -(maxHeightDp / 2) * density
|
||||||
|
val secondCardPlace = WalletCardTransitionState().copy(
|
||||||
|
alpha = 0.65f,
|
||||||
|
xTranslation = 0f,
|
||||||
|
yTranslation = ((maxHeightDp * (1 - 0.83f)) / 4 + 12) * density,
|
||||||
|
rotation = 0f,
|
||||||
|
xScale = 0.83f,
|
||||||
|
yScale = 0.83f,
|
||||||
|
)
|
||||||
|
val thirdCardPlace = WalletCardTransitionState().copy(
|
||||||
|
alpha = if (threeCards) 0.55f else 0f,
|
||||||
|
xTranslation = 0f,
|
||||||
|
yTranslation = (maxHeightDp * (1 - 0.72f) / 4 + 24) * density,
|
||||||
|
rotation = 0f,
|
||||||
|
xScale = 0.72f,
|
||||||
|
yScale = 0.72f,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (step == WalletArtworksState.Leapfrog.Step.Second) {
|
||||||
|
listOf(
|
||||||
|
CardsTransitionState(
|
||||||
|
walletCard1 = WalletCardTransitionState(),
|
||||||
|
walletCard2 = secondCardPlace,
|
||||||
|
walletCard3 = thirdCardPlace,
|
||||||
|
),
|
||||||
|
CardsTransitionState(
|
||||||
|
walletCard1 = WalletCardTransitionState().copy(
|
||||||
|
yTranslation = translationY,
|
||||||
|
),
|
||||||
|
walletCard2 = WalletCardTransitionState().copy(
|
||||||
|
alpha = 1f,
|
||||||
|
zIndex = 4f,
|
||||||
|
),
|
||||||
|
walletCard3 = thirdCardPlace,
|
||||||
|
),
|
||||||
|
CardsTransitionState(
|
||||||
|
walletCard1 = WalletCardTransitionState().copy(
|
||||||
|
zIndex = 1f,
|
||||||
|
),
|
||||||
|
walletCard2 = WalletCardTransitionState().copy(
|
||||||
|
zIndex = 4f,
|
||||||
|
),
|
||||||
|
walletCard3 = secondCardPlace,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
listOf(
|
||||||
|
CardsTransitionState(
|
||||||
|
walletCard1 = WalletCardTransitionState().copy(
|
||||||
|
zIndex = 2f,
|
||||||
|
),
|
||||||
|
walletCard2 = WalletCardTransitionState().copy(
|
||||||
|
zIndex = 3f,
|
||||||
|
),
|
||||||
|
walletCard3 = secondCardPlace,
|
||||||
|
),
|
||||||
|
CardsTransitionState(
|
||||||
|
walletCard1 = WalletCardTransitionState().copy(
|
||||||
|
yTranslation = translationY,
|
||||||
|
),
|
||||||
|
walletCard2 = WalletCardTransitionState().copy(
|
||||||
|
zIndex = 3f,
|
||||||
|
yTranslation = translationY,
|
||||||
|
),
|
||||||
|
walletCard3 = WalletCardTransitionState().copy(
|
||||||
|
alpha = 1f,
|
||||||
|
zIndex = 4f,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CardsTransitionState(
|
||||||
|
walletCard1 = WalletCardTransitionState().copy(
|
||||||
|
zIndex = 1f,
|
||||||
|
),
|
||||||
|
walletCard2 = WalletCardTransitionState().copy(
|
||||||
|
zIndex = 4f,
|
||||||
|
),
|
||||||
|
walletCard3 = WalletCardTransitionState().copy(
|
||||||
|
zIndex = 5f,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true, widthDp = 360, heightDp = 640)
|
||||||
|
@Composable
|
||||||
|
private fun Preview() {
|
||||||
|
TangemThemePreview {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
var state: WalletArtworksState by remember { mutableStateOf(WalletArtworksState.Folded) }
|
||||||
|
|
||||||
|
WalletArtworks(
|
||||||
|
url = null,
|
||||||
|
state = state,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
|
||||||
|
var index by remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
|
Button(
|
||||||
|
modifier = Modifier
|
||||||
|
.zIndex(5f)
|
||||||
|
.align(Alignment.TopStart),
|
||||||
|
onClick = {
|
||||||
|
val list = listOf(
|
||||||
|
WalletArtworksState.Folded,
|
||||||
|
WalletArtworksState.Stack(),
|
||||||
|
WalletArtworksState.Fan,
|
||||||
|
WalletArtworksState.Unfold(step = WalletArtworksState.Unfold.Step.First),
|
||||||
|
WalletArtworksState.Unfold(step = WalletArtworksState.Unfold.Step.Second),
|
||||||
|
WalletArtworksState.Unfold(step = WalletArtworksState.Unfold.Step.Third),
|
||||||
|
WalletArtworksState.Stack(),
|
||||||
|
WalletArtworksState.Leapfrog(step = WalletArtworksState.Leapfrog.Step.Second),
|
||||||
|
WalletArtworksState.Leapfrog(step = WalletArtworksState.Leapfrog.Step.Third),
|
||||||
|
)
|
||||||
|
|
||||||
|
state = list[index % list.size]
|
||||||
|
index++
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text("Animate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,5 @@
|
||||||
package com.tangem.features.onboarding.v2.multiwallet.impl.ui.state
|
package com.tangem.features.onboarding.v2.multiwallet.impl.ui.state
|
||||||
|
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
|
||||||
|
|
||||||
internal data class OnboardingMultiWalletUM(
|
internal data class OnboardingMultiWalletUM(
|
||||||
val onCreateWalletClick: () -> Unit,
|
val artworkUrl: String? = null,
|
||||||
val showSeedPhraseOption: Boolean,
|
)
|
||||||
val onOtherOptionsClick: () -> Unit,
|
|
||||||
val onBack: () -> Unit,
|
|
||||||
val dialog: Dialog?,
|
|
||||||
) {
|
|
||||||
data class Dialog(
|
|
||||||
val title: TextReference,
|
|
||||||
val description: TextReference,
|
|
||||||
val dismissButtonText: TextReference,
|
|
||||||
val confirmButtonText: TextReference,
|
|
||||||
val onConfirm: () -> Unit,
|
|
||||||
val onDismissButtonClick: () -> Unit,
|
|
||||||
val onDismiss: () -> Unit,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue