Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-13 13:43:06 +05:00
parent 4bb7fde041
commit 4341673767
20 changed files with 276 additions and 351 deletions

View file

@ -33,7 +33,6 @@ class AccountPortfolioItemUMConverter(
endIcon = endIcon,
onClick = { onClick(value.accountId) },
imageState = getImageState(value),
label = null,
)
}
}

View file

@ -36,9 +36,6 @@ import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.account.AccountIconSize
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.components.label.Label
import com.tangem.core.ui.components.label.entity.LabelStyle
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
@ -76,8 +73,6 @@ fun UserWalletItem(
balance = state.balance,
)
state.label?.let { Label(it) }
when (state.endIcon) {
UserWalletItemUM.EndIcon.None -> Unit
UserWalletItemUM.EndIcon.Arrow -> {
@ -94,6 +89,13 @@ fun UserWalletItem(
contentDescription = null,
)
}
UserWalletItemUM.EndIcon.Warning -> {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_alert_circle_24),
tint = TangemTheme.colors.icon.warning,
contentDescription = null,
)
}
}
}
}
@ -316,10 +318,7 @@ private class UserWalletItemUMPreviewProvider : PreviewParameterProvider<UserWal
name = stringReference("Mobile Wallet"),
information = getInformation(cardCount = 1),
balance = UserWalletItemUM.Balance.Locked,
label = LabelUM(
text = resourceReference(R.string.hw_backup_no_backup),
style = LabelStyle.WARNING,
),
endIcon = UserWalletItemUM.EndIcon.Warning,
isEnabled = true,
onClick = {},
),

View file

@ -2,10 +2,7 @@ package com.tangem.common.ui.userwallet.converter
import com.tangem.common.ui.R
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.ui.components.label.entity.LabelStyle
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.fiat
@ -52,7 +49,6 @@ class UserWalletItemUMConverter(
endIcon = endIcon,
onClick = { onClick(value.walletId) },
imageState = artwork,
label = getLabelOrNull(userWallet = this),
)
}
}
@ -61,17 +57,6 @@ class UserWalletItemUMConverter(
return isAuthMode || userWallet.isLocked.not()
}
private fun getLabelOrNull(userWallet: UserWallet): LabelUM? {
return if (isAuthMode.not() && userWallet is UserWallet.Hot && !userWallet.backedUp) {
LabelUM(
text = resourceReference(R.string.hw_backup_no_backup),
style = LabelStyle.WARNING,
)
} else {
null
}
}
private fun getInfo(userWallet: UserWallet): UserWalletItemUM.Information.Loaded {
val text = when (userWallet) {
is UserWallet.Cold -> {

View file

@ -2,7 +2,6 @@ package com.tangem.common.ui.userwallet.state
import com.tangem.common.ui.account.CryptoPortfolioIconUM
import com.tangem.core.ui.components.artwork.ArtworkUM
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.wallet.UserWalletId
import javax.annotation.concurrent.Immutable
@ -17,12 +16,13 @@ data class UserWalletItemUM(
val isEnabled: Boolean,
val endIcon: EndIcon = EndIcon.None,
val onClick: () -> Unit,
val label: LabelUM? = null,
) {
enum class EndIcon {
None,
Arrow,
Checkmark,
Warning,
}
sealed class Balance {

View file

@ -51,7 +51,19 @@ fun BlockItem(model: BlockUM, modifier: Modifier = Modifier) {
overflow = TextOverflow.Ellipsis,
)
model.label?.let { Label(it) }
when (val endContent = model.endContent) {
is BlockUM.EndContent.None -> Unit
is BlockUM.EndContent.Icon -> Icon(
painter = painterResource(id = endContent.resId),
contentDescription = null,
tint = when (endContent.accentType) {
BlockUM.AccentType.NONE -> TangemTheme.colors.text.primary1
BlockUM.AccentType.ACCENT -> TangemTheme.colors.text.accent
BlockUM.AccentType.WARNING -> TangemTheme.colors.text.warning
},
)
is BlockUM.EndContent.Label -> Label(endContent.label)
}
}
}
}

View file

@ -3,15 +3,29 @@ package com.tangem.core.ui.components.block.model
import androidx.annotation.DrawableRes
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.TextReference
import javax.annotation.concurrent.Immutable
data class BlockUM(
val text: TextReference,
@DrawableRes val iconRes: Int,
val onClick: () -> Unit,
val accentType: AccentType = AccentType.NONE,
val label: LabelUM? = null,
val endContent: EndContent = EndContent.None,
) {
@Immutable
sealed interface EndContent {
data object None : EndContent
data class Label(
val label: LabelUM,
) : EndContent
data class Icon(
val resId: Int,
val accentType: AccentType = AccentType.NONE,
) : EndContent
}
enum class AccentType {
NONE, ACCENT, WARNING,
}

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M11.413,2.426C12.757,2.426 13.43,2.426 13.943,2.688C14.395,2.918 14.762,3.285 14.992,3.736C15.254,4.25 15.253,4.922 15.253,6.266V9.736C15.253,11.079 15.254,11.752 14.992,12.265C14.762,12.716 14.395,13.084 13.943,13.314C13.43,13.575 12.757,13.575 11.413,13.575H4.587C3.243,13.575 2.57,13.575 2.057,13.314C1.605,13.084 1.238,12.716 1.008,12.265C0.746,11.752 0.746,11.079 0.746,9.736V6.266C0.746,4.922 0.746,4.25 1.008,3.736C1.238,3.285 1.605,2.918 2.057,2.688C2.57,2.426 3.243,2.426 4.587,2.426H11.413ZM8,5.063C7.876,5.063 7.814,5.063 7.763,5.073C7.551,5.115 7.386,5.281 7.344,5.492C7.334,5.544 7.333,5.606 7.333,5.729V7.334H5.729C5.606,7.334 5.544,7.334 5.492,7.344C5.281,7.386 5.115,7.551 5.073,7.763C5.063,7.815 5.063,7.877 5.063,8.001C5.063,8.124 5.063,8.186 5.073,8.238C5.115,8.449 5.281,8.615 5.492,8.657C5.544,8.667 5.606,8.667 5.729,8.667H7.333V10.271C7.333,10.394 7.334,10.456 7.344,10.508C7.386,10.719 7.551,10.885 7.763,10.927C7.814,10.937 7.876,10.938 8,10.938C8.124,10.938 8.186,10.937 8.238,10.927C8.449,10.885 8.615,10.719 8.657,10.508C8.667,10.456 8.667,10.394 8.667,10.271V8.667H10.27C10.394,8.667 10.456,8.667 10.508,8.657C10.719,8.615 10.885,8.449 10.927,8.238C10.937,8.186 10.937,8.124 10.937,8.001C10.937,7.877 10.937,7.815 10.927,7.763C10.885,7.551 10.719,7.386 10.508,7.344C10.456,7.333 10.394,7.334 10.27,7.334H8.667V5.729C8.667,5.606 8.667,5.544 8.657,5.492C8.615,5.281 8.449,5.115 8.238,5.073C8.186,5.063 8.124,5.063 8,5.063Z"
android:fillColor="#0099FF"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M12.054,1.505C13.173,1.505 13.733,1.505 14.161,1.723C14.537,1.914 14.843,2.221 15.035,2.597C15.253,3.024 15.253,3.585 15.253,4.705V11.296C15.253,12.416 15.253,12.976 15.035,13.404C14.843,13.781 14.537,14.087 14.161,14.278C13.733,14.496 13.173,14.496 12.054,14.496H3.946C2.827,14.496 2.267,14.496 1.839,14.278C1.463,14.087 1.157,13.78 0.965,13.404C0.747,12.976 0.746,12.416 0.746,11.296V4.705C0.746,3.585 0.747,3.024 0.965,2.597C1.157,2.221 1.463,1.914 1.839,1.723C2.267,1.505 2.827,1.505 3.946,1.505H12.054ZM3.597,10.534C3.21,10.535 2.897,10.848 2.896,11.234C2.896,11.621 3.21,11.934 3.597,11.935H6.563C6.949,11.935 7.263,11.621 7.263,11.234C7.263,10.848 6.949,10.534 6.563,10.534H3.597ZM10.921,6.947C10.535,6.947 10.221,7.261 10.221,7.647V9.544L9.72,9.043C9.446,8.77 9.003,8.77 8.729,9.043C8.456,9.316 8.457,9.76 8.729,10.033L10.426,11.729C10.699,12.002 11.142,12.002 11.415,11.729L13.111,10.034C13.384,9.761 13.384,9.317 13.111,9.044C12.838,8.771 12.394,8.77 12.121,9.043L11.62,9.544V7.647C11.62,7.261 11.307,6.947 10.921,6.947ZM3.597,7.301C3.21,7.301 2.897,7.615 2.896,8.001C2.897,8.387 3.21,8.701 3.597,8.701H6.563C6.949,8.701 7.262,8.387 7.263,8.001C7.263,7.615 6.949,7.301 6.563,7.301H3.597ZM3.597,4.066C3.21,4.067 2.897,4.38 2.896,4.767C2.896,5.153 3.21,5.467 3.597,5.467H6.563C6.949,5.467 7.263,5.153 7.263,4.767C7.263,4.38 6.949,4.066 6.563,4.066H3.597ZM9.438,4.066C9.051,4.067 8.737,4.38 8.737,4.767C8.737,5.153 9.051,5.467 9.438,5.467H12.403C12.79,5.467 13.104,5.153 13.104,4.767C13.103,4.38 12.79,4.067 12.403,4.066H9.438Z"
android:fillColor="#0099FF"/>
</vector>

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M4.559,7.397C5.105,7.397 5.547,7.839 5.547,8.385V13.432C5.547,13.977 5.105,14.419 4.559,14.419H1.91C1.364,14.419 0.922,13.977 0.922,13.432V8.385C0.922,7.839 1.364,7.397 1.91,7.397H4.559ZM2.773,8.382C2.482,8.382 2.246,8.617 2.246,8.908C2.246,9.199 2.482,9.435 2.773,9.435H3.695C3.986,9.435 4.221,9.199 4.221,8.908C4.221,8.617 3.986,8.382 3.695,8.382H2.773Z"
android:fillColor="#0099FF"
android:fillType="evenOdd"/>
<path
android:pathData="M9.376,1.58C9.887,1.58 10.267,1.58 10.596,1.668C11.485,1.905 12.179,2.598 12.417,3.484C12.473,3.693 12.493,3.923 12.501,4.194C12.597,4.203 12.689,4.213 12.778,4.225C13.375,4.305 13.902,4.479 14.324,4.9C14.746,5.322 14.922,5.848 15.002,6.443C15.078,7.011 15.078,7.727 15.078,8.596V9.971C15.078,10.84 15.078,11.557 15.002,12.124C14.922,12.72 14.746,13.246 14.324,13.667C13.902,14.089 13.375,14.263 12.778,14.343C12.209,14.419 11.491,14.419 10.619,14.419H6.666C6.658,14.419 6.65,14.419 6.642,14.419C6.784,14.12 6.865,13.785 6.865,13.432V8.385C6.865,7.112 5.833,6.08 4.56,6.08H1.91C1.557,6.08 1.221,6.16 0.922,6.302V3.506C0.922,2.442 1.786,1.58 2.852,1.58H9.376ZM12.271,8.29C11.645,8.29 11.138,8.797 11.138,9.423C11.138,10.048 11.645,10.555 12.271,10.555C12.896,10.555 13.404,10.048 13.404,9.423C13.403,8.797 12.896,8.29 12.271,8.29ZM2.852,2.864C2.497,2.864 2.208,3.151 2.208,3.506C2.208,3.86 2.497,4.148 2.852,4.148H10.619C10.825,4.148 11.022,4.148 11.211,4.149C11.205,3.983 11.194,3.892 11.173,3.816C11.054,3.373 10.707,3.026 10.263,2.908C10.12,2.869 9.926,2.864 9.286,2.864H2.852Z"
android:fillColor="#0099FF"
android:fillType="evenOdd"/>
</vector>

View file

@ -125,7 +125,6 @@ internal object PortfolioSelectorPreviewData {
name = accountName,
icon = AccountIconPreviewData.randomAccountIcon(),
),
label = null,
)
private val walletItem: UserWalletItemUM
@ -137,7 +136,6 @@ internal object PortfolioSelectorPreviewData {
isEnabled = true,
onClick = { },
imageState = ImageState.MobileWallet,
label = null,
)
private val lockedWalletItem: UserWalletItemUM

View file

@ -1,72 +1,80 @@
package com.tangem.features.createwalletselection
import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic.SignedIn
import com.tangem.core.analytics.models.Basic.SignedIn.SignInType
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.R
import com.tangem.core.ui.components.label.entity.LabelStyle
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.analytics.IntroductionProcess
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
import com.tangem.domain.card.analytics.Shop
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.SaveWalletError
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.features.createwalletselection.entity.CreateWalletSelectionUM
import com.tangem.features.createwalletselection.impl.R
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
private const val HIDE_PROGRESS_DELAY = 400L
@Suppress("LongParameterList")
@ModelScoped
internal class CreateWalletSelectionModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val scanCardProcessor: ScanCardProcessor,
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val settingsRepository: SettingsRepository,
private val analyticsEventHandler: AnalyticsEventHandler,
private val appRouter: AppRouter,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val saveWalletUseCase: SaveWalletUseCase,
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
private val urlOpener: UrlOpener,
private val userWalletsListRepository: UserWalletsListRepository,
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
) : Model() {
internal val uiState: StateFlow<CreateWalletSelectionUM>
field = MutableStateFlow(
CreateWalletSelectionUM(
onBackClick = { router.pop() },
onMobileWalletClick = ::onMobileWalletClick,
onHardwareWalletClick = ::onHardwareWalletClick,
onScanClick = ::onScanClick,
blocks = persistentListOf(
CreateWalletSelectionUM.Block(
title = resourceReference(R.string.wallet_create_hardware_title),
titleLabel = LabelUM(
text = resourceReference(R.string.common_recommended),
style = LabelStyle.ACCENT,
),
description = resourceReference(R.string.wallet_add_hardware_description),
features = persistentListOf(
CreateWalletSelectionUM.Feature(
iconResId = R.drawable.ic_add_wallet_16,
title = resourceReference(R.string.wallet_add_hardware_info_create),
),
CreateWalletSelectionUM.Feature(
iconResId = R.drawable.ic_import_seed_16,
title = resourceReference(R.string.wallet_add_import_seed_phrase),
),
),
onClick = ::onHardwareWalletClick,
),
CreateWalletSelectionUM.Block(
title = resourceReference(R.string.wallet_create_mobile_title),
titleLabel = null,
description = resourceReference(R.string.wallet_add_mobile_description),
features = persistentListOf(
CreateWalletSelectionUM.Feature(
iconResId = R.drawable.ic_mobile_wallet_16,
title = resourceReference(R.string.hw_create_title),
),
CreateWalletSelectionUM.Feature(
iconResId = R.drawable.ic_import_seed_16,
title = resourceReference(R.string.wallet_add_import_seed_phrase),
),
),
onClick = ::onMobileWalletClick,
),
),
onBuyClick = ::onBuyClick,
),
)
@ -86,6 +94,10 @@ internal class CreateWalletSelectionModel @Inject constructor(
}
private fun onHardwareWalletClick() {
// TODO [REDACTED_TASK_KEY]
}
private fun onBuyClick() {
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards)
analyticsEventHandler.send(Shop.ScreenOpened)
modelScope.launch {
@ -93,113 +105,6 @@ internal class CreateWalletSelectionModel @Inject constructor(
}
}
private fun onScanClick() {
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard)
scanCard()
}
private fun scanCard() {
modelScope.launch {
setLoading(true)
val shouldSaveAccessCodes = settingsRepository.shouldSaveAccessCodes()
cardSdkConfigRepository.setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = shouldSaveAccessCodes,
)
val analyticsSource = AnalyticsParam.ScreensSources.Intro
scanCardProcessor.scan(
analyticsSource = analyticsSource,
onProgressStateChange = { showProgress ->
if (!showProgress) {
delay(HIDE_PROGRESS_DELAY)
setLoading(false)
} else {
setLoading(true)
}
},
onFailure = { error ->
handleScanError(error)
delay(HIDE_PROGRESS_DELAY)
setLoading(false)
},
onSuccess = { scanResponse ->
proceedWithScanResponse(scanResponse)
},
)
}
}
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) {
val userWallet = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build()
if (userWallet == null) {
Timber.e("User wallet not created")
setLoading(false)
return
}
saveWalletUseCase(userWallet = userWallet).fold(
ifLeft = {
delay(HIDE_PROGRESS_DELAY)
setLoading(false)
when (it) {
is SaveWalletError.DataError -> Timber.e(it.toString(), "Unable to save user wallet")
is SaveWalletError.WalletAlreadySaved -> {
userWalletsListRepository.unlock(
userWalletId = userWallet.walletId,
unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse),
).onRight {
appRouter.replaceAll(AppRoute.Wallet)
}
}
}
},
ifRight = {
setLoading(false)
sendSignedInCardAnalyticsEvent(scanResponse)
appRouter.replaceAll(AppRoute.Wallet)
},
)
}
private suspend fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
if (currency != null) {
analyticsEventHandler.send(
SignedIn(
currency = currency,
batch = scanResponse.card.batchId,
signInType = SignInType.Card,
walletsCount = userWalletsListRepository.userWalletsSync().size.toString(),
hasBackup = scanResponse.card.backupStatus?.isActive,
),
)
}
}
private fun setLoading(isLoading: Boolean) {
uiState.update { it.copy(isScanInProgress = isLoading) }
}
fun handleScanError(error: TangemError) {
when (error) {
is TangemSdkError.NfcFeatureIsUnavailable -> handleNfcFeatureUnavailable()
is TangemSdkError -> Timber.e(error, "Scan error occurred")
else -> Timber.e(error, "Error happened")
}
}
private fun handleNfcFeatureUnavailable() {
uiMessageSender.send(
message = DialogMessage(
message = resourceReference(R.string.nfc_error_unavailable),
title = resourceReference(id = R.string.common_error),
),
)
}
companion object {
private const val SHOW_ALREADY_HAVE_WALLET_DELAY = 3000L
}

View file

@ -1,11 +1,26 @@
package com.tangem.features.createwalletselection.entity
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
internal data class CreateWalletSelectionUM(
val isScanInProgress: Boolean = false,
val hardwareWalletPrice: String = "$54.90",
val showAlreadyHaveWallet: Boolean = false,
val blocks: ImmutableList<Block>,
val onBackClick: () -> Unit,
val onMobileWalletClick: () -> Unit,
val onHardwareWalletClick: () -> Unit,
val onScanClick: () -> Unit,
val onBuyClick: () -> Unit,
) {
data class Block(
val title: TextReference,
val titleLabel: LabelUM?,
val description: TextReference,
val features: ImmutableList<Feature>,
val onClick: () -> Unit,
)
data class Feature(
val iconResId: Int,
val title: TextReference,
)
}

View file

@ -10,23 +10,25 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.buttons.common.TangemButton
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.components.label.Label
import com.tangem.core.ui.components.label.entity.LabelStyle
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.createwalletselection.entity.CreateWalletSelectionUM
import com.tangem.features.createwalletselection.impl.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Suppress("LongMethod")
@OptIn(ExperimentalMaterial3Api::class)
@ -34,7 +36,7 @@ import com.tangem.features.createwalletselection.impl.R
internal fun CreateWalletSelectionContent(state: CreateWalletSelectionUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.background(TangemTheme.colors.background.primary)
.background(TangemTheme.colors.background.secondary)
.fillMaxSize()
.systemBarsPadding(),
) {
@ -42,7 +44,7 @@ internal fun CreateWalletSelectionContent(state: CreateWalletSelectionUM, modifi
modifier = Modifier
.statusBarsPadding(),
colors = TopAppBarDefaults.topAppBarColors(
containerColor = TangemTheme.colors.background.primary,
containerColor = TangemTheme.colors.background.secondary,
),
navigationIcon = {
IconButton(onClick = state.onBackClick) {
@ -58,7 +60,7 @@ internal fun CreateWalletSelectionContent(state: CreateWalletSelectionUM, modifi
Text(
modifier = Modifier
.padding(16.dp),
text = stringResourceSafe(R.string.wallet_create_nav_info_title),
text = stringResourceSafe(R.string.wallet_add_support_title),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
maxLines = 1,
@ -78,60 +80,33 @@ internal fun CreateWalletSelectionContent(state: CreateWalletSelectionUM, modifi
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
text = stringResourceSafe(R.string.wallet_create_title),
.padding(
start = 16.dp,
end = 16.dp,
bottom = 24.dp,
),
text = stringResourceSafe(R.string.wallet_add_common_title),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
state.blocks.forEach { block ->
WalletBlock(
modifier = Modifier
.padding(top = 24.dp),
title = stringResourceSafe(R.string.wallet_create_mobile_title),
description = stringResourceSafe(R.string.wallet_create_mobile_description),
badge = {
Box(
modifier = Modifier
.background(
color = TangemTheme.colors.field.focused,
shape = TangemTheme.shapes.roundedCorners8,
)
.padding(horizontal = 8.dp, vertical = 4.dp),
) {
Text(
text = stringResourceSafe(R.string.common_free),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.secondary,
.padding(top = 8.dp),
title = block.title.resolveReference(),
description = block.description.resolveReference(),
features = block.features,
badge = block.titleLabel?.let {
{ Label(it) }
},
onClick = block.onClick,
)
}
},
onClick = state.onMobileWalletClick,
)
WalletBlock(
title = stringResourceSafe(R.string.wallet_create_hardware_title),
description = stringResourceSafe(R.string.wallet_create_hardware_description),
badge = {
Box(
modifier = Modifier
.background(
color = TangemTheme.colors.text.accent.copy(alpha = 0.1f),
shape = TangemTheme.shapes.roundedCorners8,
)
.padding(horizontal = 8.dp, vertical = 4.dp),
) {
Text(
text = stringResourceSafe(R.string.wallet_create_hardware_badge, state.hardwareWalletPrice),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.accent,
)
}
},
onClick = state.onHardwareWalletClick,
)
}
AnimatedVisibility(state.showAlreadyHaveWallet) {
AlreadyHaveTangemWalletBlock(
onScanClick = state.onScanClick,
onBuyClick = state.onBuyClick,
isScanInProgress = state.isScanInProgress,
)
}
@ -143,8 +118,9 @@ private fun WalletBlock(
title: String,
description: String,
onClick: () -> Unit,
features: ImmutableList<CreateWalletSelectionUM.Feature>,
modifier: Modifier = Modifier,
badge: @Composable () -> Unit,
badge: @Composable (() -> Unit)? = null,
) {
Column(
modifier = modifier
@ -152,11 +128,14 @@ private fun WalletBlock(
.padding(top = 8.dp)
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(
color = TangemTheme.colors.field.primary,
color = TangemTheme.colors.background.primary,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.clickable(onClick = onClick)
.padding(16.dp),
.padding(
horizontal = 16.dp,
vertical = 12.dp,
),
) {
Row {
Text(
@ -167,7 +146,7 @@ private fun WalletBlock(
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
badge()
badge?.invoke()
}
Text(
modifier = Modifier
@ -176,24 +155,57 @@ private fun WalletBlock(
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
if (features.isNotEmpty()) {
HorizontalDivider(
modifier = Modifier.padding(top = 12.dp),
thickness = 0.5.dp,
color = TangemTheme.colors.stroke.primary,
)
features.forEach {
Feature(
feature = it,
modifier = Modifier
.padding(top = 12.dp),
)
}
}
}
}
@Composable
private fun Feature(feature: CreateWalletSelectionUM.Feature, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size16),
painter = painterResource(id = feature.iconResId),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
)
Text(
modifier = Modifier
.weight(1f, fill = false)
.padding(start = 6.dp),
text = feature.title.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.secondary,
)
}
}
@Composable
private fun AlreadyHaveTangemWalletBlock(
onScanClick: () -> Unit,
onBuyClick: () -> Unit,
isScanInProgress: Boolean,
modifier: Modifier = Modifier,
) {
var buttonWidth by remember { mutableStateOf(0) }
val density = LocalDensity.current
Row(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
.background(
color = TangemTheme.colors.field.primary,
color = TangemTheme.colors.background.primary,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.padding(
@ -206,30 +218,16 @@ private fun AlreadyHaveTangemWalletBlock(
modifier = Modifier
.weight(1f)
.padding(end = 16.dp),
text = stringResourceSafe(R.string.wallet_create_scan_question),
text = stringResourceSafe(R.string.wallet_add_hardware_purchase),
style = TangemTheme.typography.button,
color = TangemTheme.colors.text.primary1,
)
TangemButton(
modifier = Modifier
.conditional(buttonWidth > 0) {
width(with(density) { buttonWidth.toDp() })
}
.onGloballyPositioned { coordinates ->
if (buttonWidth == 0) {
buttonWidth = coordinates.size.width
}
},
text = stringResourceSafe(R.string.wallet_create_scan_title),
onClick = onScanClick,
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24),
SecondaryButton(
text = stringResourceSafe(R.string.wallet_import_buy_title),
onClick = onBuyClick,
size = TangemButtonSize.RoundedAction,
showProgress = isScanInProgress,
colors = TangemButtonsDefaults.secondaryButtonColors,
textStyle = TangemTheme.typography.subtitle1,
enabled = true,
animateContentChange = true,
)
}
}
@ -241,11 +239,45 @@ private fun PreviewCreateWalletContent() {
TangemThemePreview {
CreateWalletSelectionContent(
state = CreateWalletSelectionUM(
showAlreadyHaveWallet = true,
onBackClick = { },
onMobileWalletClick = {},
onHardwareWalletClick = {},
onScanClick = {},
blocks = persistentListOf(
CreateWalletSelectionUM.Block(
title = resourceReference(R.string.wallet_create_hardware_title),
titleLabel = LabelUM(
text = resourceReference(R.string.common_recommended),
style = LabelStyle.ACCENT,
),
description = resourceReference(R.string.wallet_add_hardware_description),
features = persistentListOf(
CreateWalletSelectionUM.Feature(
iconResId = R.drawable.ic_add_wallet_16,
title = resourceReference(R.string.wallet_add_hardware_info_create),
),
CreateWalletSelectionUM.Feature(
iconResId = R.drawable.ic_import_seed_16,
title = resourceReference(R.string.wallet_add_import_seed_phrase),
),
),
onClick = { },
),
CreateWalletSelectionUM.Block(
title = resourceReference(R.string.wallet_create_mobile_title),
titleLabel = null,
description = resourceReference(R.string.wallet_add_mobile_description),
features = persistentListOf(
CreateWalletSelectionUM.Feature(
iconResId = R.drawable.ic_mobile_wallet_16,
title = resourceReference(R.string.hw_create_title),
),
CreateWalletSelectionUM.Feature(
iconResId = R.drawable.ic_import_seed_16,
title = resourceReference(R.string.wallet_add_import_seed_phrase),
),
),
onClick = { },
),
),
onBuyClick = { },
),
)
}

View file

@ -2,7 +2,6 @@ package com.tangem.features.details.entity
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
@ -12,5 +11,4 @@ internal data class UserWalletListUM(
val isWalletSavingInProgress: Boolean,
val addNewWalletText: TextReference,
val onAddNewWalletClick: () -> Unit,
val addWalletBottomSheet: TangemBottomSheetConfig = TangemBottomSheetConfig.Empty,
)

View file

@ -6,14 +6,8 @@ import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.R.*
import com.tangem.core.ui.components.bottomsheets.BottomSheetOption
import com.tangem.core.ui.components.bottomsheets.OptionsBottomSheetContent
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.features.details.entity.UserWalletListUM
import com.tangem.features.details.impl.R
@ -27,7 +21,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@Suppress("LongParameterList")
@ -38,8 +31,6 @@ internal class UserWalletListModel @Inject constructor(
private val router: Router,
private val messageSender: UiMessageSender,
override val dispatchers: CoroutineDispatcherProvider,
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
private val urlOpener: UrlOpener,
private val userWalletSaver: UserWalletSaver,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) : Model() {
@ -58,7 +49,6 @@ internal class UserWalletListModel @Inject constructor(
isWalletSavingInProgress = false,
addNewWalletText = TextReference.EMPTY,
onAddNewWalletClick = ::onAddNewWalletClick,
addWalletBottomSheet = TangemBottomSheetConfig.Empty,
),
)
@ -90,62 +80,11 @@ internal class UserWalletListModel @Inject constructor(
private fun onAddNewWalletClick() {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
state.update { currentState ->
currentState.copy(
addWalletBottomSheet = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = ::dismissAddWalletBottomSheet,
content = createAddWalletBottomSheetContent(),
),
)
}
router.push(AppRoute.CreateWalletSelection)
} else {
withProgress(isWalletSavingInProgress) {
userWalletSaver.scanAndSaveUserWallet(modelScope)
}
}
}
private fun dismissAddWalletBottomSheet() {
state.update { currentState ->
currentState.copy(
addWalletBottomSheet = currentState.addWalletBottomSheet.copy(isShown = false),
)
}
}
private fun createAddWalletBottomSheetContent(): OptionsBottomSheetContent {
return OptionsBottomSheetContent(
options = persistentListOf(
BottomSheetOption(
key = ADD_WALLET_KEY_CREATE,
label = resourceReference(string.home_button_create_new_wallet),
),
BottomSheetOption(
key = ADD_WALLET_KEY_ADD,
label = resourceReference(string.home_button_add_existing_wallet),
),
BottomSheetOption(
key = ADD_WALLET_KEY_BUY,
label = resourceReference(string.details_buy_wallet),
),
),
onOptionClick = { optionKey ->
dismissAddWalletBottomSheet()
when (optionKey) {
ADD_WALLET_KEY_CREATE -> router.push(AppRoute.CreateWalletSelection)
ADD_WALLET_KEY_ADD -> router.push(AppRoute.AddExistingWallet)
ADD_WALLET_KEY_BUY -> modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}
}
},
)
}
companion object {
private const val ADD_WALLET_KEY_CREATE = "create"
private const val ADD_WALLET_KEY_ADD = "add"
private const val ADD_WALLET_KEY_BUY = "buy"
}
}

View file

@ -15,13 +15,9 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.common.ui.userwallet.UserWalletItem
import com.tangem.core.ui.R.*
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.bottomsheets.OptionsBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.details.component.UserWalletListComponent
@ -48,8 +44,6 @@ internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = M
onClick = state.onAddNewWalletClick,
)
}
AddWalletBottomSheet(state.addWalletBottomSheet)
}
@Composable
@ -100,15 +94,6 @@ private fun AddWalletButton(
}
}
@Composable
private fun AddWalletBottomSheet(config: TangemBottomSheetConfig) {
OptionsBottomSheet(
config = config,
title = resourceReference(string.auth_info_add_wallet_title),
containerColor = TangemTheme.colors.background.tertiary,
)
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)

View file

@ -39,7 +39,6 @@ internal class PreviewWalletSettingsComponent : WalletSettingsComponent {
name = accountName,
icon = AccountIconPreviewData.randomAccountIcon(),
),
label = null,
)
private val previewState = WalletSettingsUM(

View file

@ -197,7 +197,12 @@ internal class WalletSettingsModel @Inject constructor(
onCheckedNFTChange = ::onCheckedNFTChange,
forgetWallet = {
val message = DialogMessage(
message = resourceReference(R.string.user_wallet_list_delete_prompt),
message = resourceReference(
id = when (userWallet) {
is UserWallet.Cold -> R.string.user_wallet_list_delete_prompt
is UserWallet.Hot -> R.string.user_wallet_list_delete_hw_prompt
},
),
firstActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.common_delete),

View file

@ -191,12 +191,14 @@ internal class ItemsBuilder @Inject constructor(
text = resourceReference(R.string.common_backup),
iconRes = R.drawable.ic_more_cards_24,
onClick = { router.push(AppRoute.WalletBackup(userWalletId)) },
label = if (hasBackup) {
null
endContent = if (hasBackup) {
BlockUM.EndContent.None
} else {
LabelUM(
BlockUM.EndContent.Label(
label = LabelUM(
text = resourceReference(R.string.hw_backup_no_backup),
style = LabelStyle.WARNING,
),
)
},
).let(::add)

View file

@ -130,6 +130,11 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
balance = balance,
isBalanceHidden = balanceHidingSettings.isBalanceHidden,
artwork = artworks[userWallet.walletId],
endIcon = if (isAuthMode.not() && userWallet is UserWallet.Hot && !userWallet.backedUp) {
UserWalletItemUM.EndIcon.Warning
} else {
UserWalletItemUM.EndIcon.None
},
isAuthMode = isAuthMode,
)
.convert(userWallet)