Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-22 16:14:14 +03:00
parent 081128f2cf
commit 63b07cccc3
53 changed files with 614 additions and 464 deletions

View file

@ -92,7 +92,7 @@ internal class CreateWalletStartModel @Inject constructor(
onPrimaryButtonClick = ::onBuyClick,
primaryButtonText = resourceReference(R.string.details_buy_wallet),
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_mobile_title),
otherMethodDescription = resourceReference(R.string.welcome_create_wallet_mobile_description),
otherMethodDescription = null,
otherMethodClick = ::onStartWithMobileWalletClick,
onBackClick = { router.pop() },
onScanClick = ::onScanClick,

View file

@ -12,7 +12,7 @@ internal data class CreateWalletStartUM(
val shouldShowScanSecondaryButton: Boolean,
val primaryButtonText: TextReference,
val onPrimaryButtonClick: () -> Unit,
val otherMethodDescription: TextReference,
val otherMethodDescription: TextReference?,
val otherMethodTitle: TextReference,
val otherMethodClick: () -> Unit,
val onScanClick: () -> Unit,

View file

@ -181,7 +181,7 @@ internal fun CreateWalletStartContent(state: CreateWalletStartUM, modifier: Modi
.height(16.dp),
)
Text(
text = stringResourceSafe(R.string.welcome_create_wallet_other_method),
text = stringResourceSafe(R.string.common_or),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
@ -193,19 +193,21 @@ internal fun CreateWalletStartContent(state: CreateWalletStartUM, modifier: Modi
.scale(scaleX = -1f, scaleY = 1f),
)
}
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 16.dp,
top = 16.dp,
end = 16.dp,
),
text = state.otherMethodDescription.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
if (state.otherMethodDescription != null) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 16.dp,
top = 16.dp,
end = 16.dp,
),
text = state.otherMethodDescription.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
}
Row(
modifier = Modifier
.wrapContentWidth()
@ -429,9 +431,7 @@ private class CreateWalletStartStateProvider : CollectionPreviewParameterProvide
onPrimaryButtonClick = { },
primaryButtonText = resourceReference(R.string.details_buy_wallet),
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_mobile_title),
otherMethodDescription = resourceReference(
R.string.welcome_create_wallet_mobile_description,
),
otherMethodDescription = null,
otherMethodClick = { },
onBackClick = { },
onScanClick = { },

View file

@ -12,6 +12,7 @@ import com.tangem.features.details.entity.DetailsUM
import com.tangem.features.details.ui.DetailsScreen
import com.tangem.features.details.utils.ItemsBuilder
import com.tangem.features.details.utils.SocialsBuilder
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import kotlinx.coroutines.runBlocking
internal class PreviewDetailsComponent : DetailsComponent {
@ -19,9 +20,14 @@ internal class PreviewDetailsComponent : DetailsComponent {
private val previewBlocks = runBlocking {
ItemsBuilder(
router = DummyRouter(),
hotWalletFeatureToggles = object : HotWalletFeatureToggles {
override val isHotWalletEnabled: Boolean = true
override val isWalletCreationRestrictionEnabled: Boolean = true
},
).buildAll(
isWalletConnectAvailable = true,
isSupportChatAvailable = true,
hasAnyMobileWallet = true,
userWalletId = UserWalletId(""),
onSupportEmailClick = {},
onSupportChatClick = {},

View file

@ -50,6 +50,7 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent {
),
addNewWalletText = resourceReference(R.string.user_wallet_list_add_button),
isWalletSavingInProgress = true,
addNewWalletIconRes = R.drawable.ic_plus_24,
onAddNewWalletClick = {},
),
)

View file

@ -2,6 +2,7 @@ package com.tangem.features.details.entity
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.block.model.BlockUM
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
@Immutable
@ -27,4 +28,9 @@ internal sealed class DetailsItemUM {
data object UserWalletList : DetailsItemUM() {
override val id: String = "user_wallet_list"
}
data class UnderSectionText(
override val id: String,
val text: TextReference,
) : DetailsItemUM()
}

View file

@ -1,5 +1,6 @@
package com.tangem.features.details.entity
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.ui.extensions.TextReference
@ -10,5 +11,6 @@ internal data class UserWalletListUM(
val userWallets: ImmutableList<UserWalletItemUM>,
val isWalletSavingInProgress: Boolean,
val addNewWalletText: TextReference,
@DrawableRes val addNewWalletIconRes: Int?,
val onAddNewWalletClick: () -> Unit,
)

View file

@ -97,6 +97,7 @@ internal class DetailsModel @Inject constructor(
itemsBuilder.buildAll(
isWalletConnectAvailable = isWalletConnectAvailable,
isSupportChatAvailable = feedbackFeatureToggles.isUsedeskEnabled,
hasAnyMobileWallet = getWalletsUseCase.invokeSync().any { it is UserWallet.Hot },
userWalletId = params.userWalletId,
onSupportEmailClick = ::sendFeedback,
onSupportChatClick = ::openUseDesk,

View file

@ -60,6 +60,7 @@ internal class UserWalletListModel @Inject constructor(
isWalletSavingInProgress = false,
addNewWalletText = TextReference.EMPTY,
onAddNewWalletClick = ::onAddNewWalletClick,
addNewWalletIconRes = R.drawable.ic_plus_24,
),
)
@ -81,10 +82,11 @@ internal class UserWalletListModel @Inject constructor(
value.copy(
userWallets = userWallets,
isWalletSavingInProgress = isWalletSavingInProgress,
addNewWalletText = if (shouldSaveUserWallets || hotWalletFeatureToggles.isHotWalletEnabled) {
resourceReference(R.string.user_wallet_list_add_button)
} else {
resourceReference(R.string.scan_card_settings_button)
addNewWalletText = when {
shouldSaveUserWallets || hotWalletFeatureToggles.isHotWalletEnabled -> {
resourceReference(R.string.user_wallet_list_add_button)
}
else -> resourceReference(R.string.scan_card_settings_button)
},
)
}
@ -92,7 +94,14 @@ internal class UserWalletListModel @Inject constructor(
private fun onAddNewWalletClick() {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
analyticsEventHandler.send(SignIn.ButtonAddWallet(AnalyticsParam.ScreensSources.Settings))
router.push(AppRoute.CreateWalletSelection)
if (hotWalletFeatureToggles.isWalletCreationRestrictionEnabled) {
withProgress(isWalletSavingInProgress) {
userWalletSaver.scanAndSaveUserWallet(modelScope)
}
} else {
router.push(AppRoute.CreateWalletSelection)
}
} else {
withProgress(isWalletSavingInProgress) {
userWalletSaver.scanAndSaveUserWallet(modelScope)

View file

@ -5,6 +5,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
@ -18,10 +19,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.block.BlockItem
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -68,7 +73,7 @@ private fun Content(
) {
LazyColumn(
modifier = modifier.testTag(DetailsScreenTestTags.SCREEN_CONTAINER),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
verticalArrangement = Arrangement.SpaceBetween,
contentPadding = PaddingValues(
top = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing16,
@ -94,6 +99,7 @@ private fun Content(
}
item(key = "footer") {
SpacerH16()
Footer(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing12),
model = state.footer,
@ -108,6 +114,16 @@ private fun Block(
userWalletListBlockContent: ComposableContentComponent,
modifier: Modifier = Modifier,
) {
if (model is DetailsItemUM.UnderSectionText) {
UnderSectionTextBlock(
modifier = Modifier.fillMaxWidth(),
text = model.text,
)
return
}
SpacerH16()
Column(
modifier = modifier
.fillMaxWidth()
@ -142,10 +158,21 @@ private fun Block(
is DetailsItemUM.UserWalletList -> {
userWalletListBlockContent.Content(modifier = itemModifier)
}
is DetailsItemUM.UnderSectionText -> { /* Handled above */ }
}
}
}
@Composable
private fun UnderSectionTextBlock(text: TextReference, modifier: Modifier = Modifier) {
Text(
modifier = modifier.padding(horizontal = 30.dp, vertical = 8.dp),
text = text.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
}
@Composable
private fun Footer(model: DetailsFooterUM, modifier: Modifier = Modifier) {
Column(

View file

@ -1,6 +1,7 @@
package com.tangem.features.details.ui
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.CircularProgressIndicator
@ -23,7 +24,6 @@ import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.details.component.UserWalletListComponent
import com.tangem.features.details.component.preview.PreviewUserWalletListComponent
import com.tangem.features.details.entity.UserWalletListUM
import com.tangem.features.details.impl.R
@Composable
internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = Modifier) {
@ -42,6 +42,7 @@ internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = M
text = state.addNewWalletText,
isInProgress = state.isWalletSavingInProgress,
onClick = state.onAddNewWalletClick,
icon = state.addNewWalletIconRes,
)
}
}
@ -49,6 +50,7 @@ internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = M
@Composable
private fun AddWalletButton(
text: TextReference,
@DrawableRes icon: Int?,
isInProgress: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
@ -65,23 +67,25 @@ private fun AddWalletButton(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
AnimatedContent(
modifier = Modifier.size(TangemTheme.dimens.size24),
targetState = isInProgress,
label = "Add wallet progress",
) { isInProgress ->
if (isInProgress) {
CircularProgressIndicator(
modifier = Modifier.size(TangemTheme.dimens.size24),
color = TangemTheme.colors.icon.accent,
)
} else {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = R.drawable.ic_plus_24),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
)
if (icon != null) {
AnimatedContent(
modifier = Modifier.size(TangemTheme.dimens.size24),
targetState = isInProgress,
label = "Add wallet progress",
) { isInProgress ->
if (isInProgress) {
CircularProgressIndicator(
modifier = Modifier.size(TangemTheme.dimens.size24),
color = TangemTheme.colors.icon.accent,
)
} else {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = icon),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
)
}
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.details.entity.DetailsItemUM
import com.tangem.features.details.impl.R
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -17,12 +18,16 @@ import javax.inject.Inject
private const val TANGEM_PAY_ITEM_ID = "get_tangem_pay"
@ModelScoped
internal class ItemsBuilder @Inject constructor(private val router: Router) {
internal class ItemsBuilder @Inject constructor(
private val router: Router,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) {
@Suppress("LongParameterList")
fun buildAll(
isWalletConnectAvailable: Boolean,
isSupportChatAvailable: Boolean,
hasAnyMobileWallet: Boolean,
userWalletId: UserWalletId,
onSupportEmailClick: () -> Unit,
onSupportChatClick: () -> Unit,
@ -30,6 +35,14 @@ internal class ItemsBuilder @Inject constructor(private val router: Router) {
): ImmutableList<DetailsItemUM> = buildList {
buildWalletConnectBlock(isWalletConnectAvailable, userWalletId)?.let(::add)
buildUserWalletListBlock().let(::add)
if (hotWalletFeatureToggles.isWalletCreationRestrictionEnabled && hasAnyMobileWallet) {
DetailsItemUM.UnderSectionText(
id = "only_one_mobile_wallet_explanation",
text = resourceReference(R.string.only_one_mobile_wallet_explanation),
).let(::add)
}
buildShopBlock(onBuyClick).let(::add)
buildSettingsBlock().let(::add)
buildSupportBlock(

View file

@ -9,6 +9,7 @@ android {
}
dependencies {
implementation(projects.common.routing)
/* Project - Domain */
implementation(projects.domain.models)

View file

@ -2,4 +2,5 @@ package com.tangem.features.hotwallet
interface HotWalletFeatureToggles {
val isHotWalletEnabled: Boolean
val isWalletCreationRestrictionEnabled: Boolean
}

View file

@ -8,6 +8,7 @@ interface WalletBackupComponent : ComposableContentComponent {
data class Params(
val userWalletId: UserWalletId,
val isColdWalletOptionShown: Boolean,
)
interface Factory : ComponentFactory<Params, WalletBackupComponent>

View file

@ -7,4 +7,7 @@ internal class DefaultHotWalletFeatureToggles(
) : HotWalletFeatureToggles {
override val isHotWalletEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "HOT_WALLET_ENABLED")
override val isWalletCreationRestrictionEnabled: Boolean
get() = isHotWalletEnabled &&
featureTogglesManager.isFeatureEnabled(name = "HOT_WALLET_CREATION_RESTRICTION_ENABLED")
}

View file

@ -26,11 +26,11 @@ private const val DISABLED_COLORS_ALPHA = 0.5f
internal fun OptionBlock(
title: String,
description: String,
badge: (@Composable () -> Unit)?,
onClick: (() -> Unit)?,
enabled: Boolean,
backgroundColor: Color,
modifier: Modifier = Modifier,
backgroundColor: Color = TangemTheme.colors.background.primary,
badge: (@Composable () -> Unit)? = null,
enabled: Boolean = true,
) {
Column(
modifier = modifier

View file

@ -66,7 +66,7 @@ internal fun CreateHardwareWalletContent(state: CreateHardwareWalletUM, modifier
top = 20.dp,
end = 16.dp,
),
text = stringResourceSafe(R.string.wallet_create_common_title),
text = stringResourceSafe(R.string.hardware_wallet_create_title),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
@ -74,22 +74,22 @@ internal fun CreateHardwareWalletContent(state: CreateHardwareWalletUM, modifier
FeatureBlock(
modifier = Modifier
.padding(top = 32.dp),
title = stringResourceSafe(R.string.hw_upgrade_key_migration_title),
description = stringResourceSafe(R.string.hw_upgrade_key_migration_description),
iconRes = R.drawable.ic_mobile_security_24,
title = stringResourceSafe(R.string.hardware_wallet_key_feature_title),
description = stringResourceSafe(R.string.hardware_wallet_key_feature_description),
iconRes = R.drawable.ic_mobile_security_2_24,
)
FeatureBlock(
modifier = Modifier
.padding(top = 24.dp),
title = stringResourceSafe(R.string.hw_upgrade_funds_access_title),
description = stringResourceSafe(R.string.hw_upgrade_funds_access_description),
iconRes = R.drawable.ic_knight_shield_24,
title = stringResourceSafe(R.string.hardware_wallet_backup_feature_title),
description = stringResourceSafe(R.string.hardware_wallet_backup_feature_description),
iconRes = R.drawable.ic_double_star_24,
)
FeatureBlock(
modifier = Modifier
.padding(top = 24.dp),
title = stringResourceSafe(R.string.hw_upgrade_general_security_title),
description = stringResourceSafe(R.string.hw_upgrade_general_security_description),
title = stringResourceSafe(R.string.hardware_wallet_security_feature_title),
description = stringResourceSafe(R.string.hardware_wallet_security_feature_description),
iconRes = R.drawable.ic_protect_24,
)
}

View file

@ -75,14 +75,21 @@ internal fun CreateMobileWalletContent(state: CreateMobileWalletUM, modifier: Mo
.padding(top = 32.dp),
title = stringResourceSafe(R.string.hw_create_keys_title),
description = stringResourceSafe(R.string.hw_create_keys_description),
iconRes = R.drawable.ic_lock_24,
iconRes = R.drawable.ic_protect_24,
)
FeatureBlock(
modifier = Modifier
.padding(top = 24.dp),
title = stringResourceSafe(R.string.hw_create_seed_title),
description = stringResourceSafe(R.string.hw_create_seed_description),
iconRes = R.drawable.ic_settings_24,
iconRes = R.drawable.ic_seed_phrase_24,
)
FeatureBlock(
modifier = Modifier
.padding(top = 24.dp),
title = stringResourceSafe(R.string.hw_create_upgrade_title),
description = stringResourceSafe(R.string.hw_create_upgrade_description),
iconRes = R.drawable.ic_tangem_card_24,
)
}
SecondaryButton(

View file

@ -83,9 +83,9 @@ internal fun UpgradeWalletContent(state: UpgradeWalletUM, modifier: Modifier = M
FeatureBlock(
modifier = Modifier
.padding(top = 24.dp),
title = stringResourceSafe(R.string.hw_upgrade_funds_access_title),
description = stringResourceSafe(R.string.hw_upgrade_funds_access_description),
iconRes = R.drawable.ic_knight_shield_24,
title = stringResourceSafe(R.string.hw_upgrade_backup_title),
description = stringResourceSafe(R.string.hw_upgrade_backup_description),
iconRes = R.drawable.ic_smartphone_24,
)
FeatureBlock(
modifier = Modifier

View file

@ -19,6 +19,7 @@ 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
@Stable
@ -48,19 +49,19 @@ internal class ViewPhraseModel @Inject constructor(
private fun loadSeedPhrase() {
val userWallet = getUserWalletUseCase(params.userWalletId)
.getOrElse { error("User wallet with id ${params.userWalletId} not found") }
if (userWallet is UserWallet.Hot) {
modelScope.launch {
val words = exportSeedPhraseUseCase.invoke(userWallet.hotWalletId)
.getOrElse { error("Unable to export seed phrase for wallet with id ${params.userWalletId}") }
.mnemonic
.mnemonicComponents
uiState.update {
it.copy(
words = words.mapIndexed { index, s ->
EnumeratedTwoColumnGridItem(index + 1, s)
}.toImmutableList(),
)
}
as? UserWallet.Hot ?: return
modelScope.launch {
val words = exportSeedPhraseUseCase.invoke(userWallet.hotWalletId)
.getOrElse { error -> Timber.e(error); throw error }
.mnemonic.mnemonicComponents
uiState.update {
it.copy(
words = words.mapIndexed { index, s ->
EnumeratedTwoColumnGridItem(index + 1, s)
}.toImmutableList(),
)
}
}
}

View file

@ -8,7 +8,6 @@ internal data class WalletBackupUM(
val recoveryPhraseOption: LabelUM?,
val googleDriveOption: LabelUM?,
val googleDriveStatus: BackupStatus,
val onBuyClick: () -> Unit,
val onRecoveryPhraseClick: () -> Unit,
val onGoogleDriveClick: () -> Unit,
val onHardwareWalletClick: () -> Unit,

View file

@ -2,21 +2,17 @@ package com.tangem.features.hotwallet.walletbackup.model
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
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.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.UnlockHotWalletContextualUseCase
import com.tangem.features.hotwallet.WalletBackupComponent
@ -36,8 +32,6 @@ internal class WalletBackupModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val unlockHotWalletContextualUseCase: UnlockHotWalletContextualUseCase,
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
private val urlOpener: UrlOpener,
private val router: Router,
private val trackingContextProxy: TrackingContextProxy,
private val analyticsEventHandler: AnalyticsEventHandler,
@ -54,7 +48,7 @@ internal class WalletBackupModel @Inject constructor(
hardwareWalletOption = LabelUM(
text = resourceReference(R.string.common_recommended),
style = LabelStyle.ACCENT,
),
).takeIf { params.isColdWalletOptionShown },
recoveryPhraseOption = LabelUM(
text = resourceReference(R.string.hw_backup_no_backup),
style = LabelStyle.WARNING,
@ -64,7 +58,6 @@ internal class WalletBackupModel @Inject constructor(
style = LabelStyle.REGULAR,
),
googleDriveStatus = BackupStatus.ComingSoon,
onBuyClick = ::onBuyClick,
onRecoveryPhraseClick = ::onRecoveryPhraseClick,
onGoogleDriveClick = { },
onHardwareWalletClick = ::onHardwareWalletClick,
@ -129,14 +122,6 @@ internal class WalletBackupModel @Inject constructor(
backedUp = userWallet.backedUp,
)
private fun onBuyClick() {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.Backup))
modelScope.launch {
generateBuyTangemCardLinkUseCase
.invoke(GenerateBuyTangemCardLinkUseCase.Source.Backup).let { urlOpener.openUrl(it) }
}
}
private fun onRecoveryPhraseClick() {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonRecoveryPhrase())
if (uiState.value.backedUp) {
@ -157,10 +142,9 @@ internal class WalletBackupModel @Inject constructor(
)
} else {
router.push(
AppRoute.CreateWalletBackup(
AppRoute.WalletActivation(
userWalletId = params.userWalletId,
analyticsSource = AnalyticsParam.ScreensSources.Backup.value,
analyticsAction = WalletSettingsAnalyticEvents.RecoveryPhraseScreenAction.Backup.value,
isBackupExists = false,
),
)
}

View file

@ -1,36 +1,25 @@
package com.tangem.features.hotwallet.walletbackup.ui
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
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.rows.NetworkTitle
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.extensions.stringResourceSafe
import com.tangem.core.ui.res.ForceDarkTheme
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.hotwallet.common.ui.OptionBlock
@ -61,33 +50,31 @@ internal fun WalletBackupContent(state: WalletBackupUM, modifier: Modifier = Mod
end = 16.dp,
),
) {
Banner(state)
if (state.hardwareWalletOption != null) {
OptionBlock(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
title = stringResourceSafe(R.string.hw_backup_upgrade_title),
description = stringResourceSafe(R.string.hw_backup_upgrade_description),
badge = { Label(state.hardwareWalletOption) },
onClick = state.onHardwareWalletClick,
enabled = true,
backgroundColor = TangemTheme.colors.background.primary,
)
OptionBlock(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
title = stringResourceSafe(R.string.hw_backup_hardware_title),
description = stringResourceSafe(R.string.hw_backup_hardware_description),
badge = {
state.hardwareWalletOption?.let { Label(it) }
},
onClick = state.onHardwareWalletClick,
enabled = true,
backgroundColor = TangemTheme.colors.background.primary,
)
NetworkTitle(
modifier = Modifier
.padding(top = 8.dp),
title = {
Text(
modifier = Modifier,
text = stringResourceSafe(R.string.onboarding_create_wallet_options_button_options),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
},
)
NetworkTitle(
modifier = Modifier.padding(top = 8.dp),
title = {
Text(
modifier = Modifier,
text = stringResourceSafe(R.string.onboarding_create_wallet_options_button_options),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
},
)
}
OptionBlock(
modifier = Modifier,
title = stringResourceSafe(R.string.hw_backup_seed_title),
@ -116,120 +103,6 @@ internal fun WalletBackupContent(state: WalletBackupUM, modifier: Modifier = Mod
}
}
@Suppress("LongMethod")
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun Banner(state: WalletBackupUM, modifier: Modifier = Modifier) {
ForceDarkTheme {
Column(
modifier = modifier
.background(
color = TangemTheme.colors.background.primary,
shape = RoundedCornerShape(16.dp),
),
) {
Column(
modifier = Modifier
.padding(
start = 12.dp,
top = 20.dp,
end = 12.dp,
),
) {
Text(
modifier = Modifier
.fillMaxWidth(),
text = "Tangem Wallet",
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
top = 4.dp,
),
text = "Keeps your crypto safe and offline. Slim as a credit card, safer than a bank vault.",
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 24.dp,
top = 16.dp,
end = 24.dp,
),
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
FeatureItem(
iconResId = R.drawable.ic_shield_check_16,
text = resourceReference(R.string.welcome_create_wallet_feature_class),
)
FeatureItem(
iconResId = R.drawable.ic_flash_16,
text = resourceReference(R.string.welcome_create_wallet_feature_delivery),
)
FeatureItem(
iconResId = R.drawable.ic_sparkles_16,
text = resourceReference(R.string.welcome_create_wallet_feature_use),
)
}
Box(
modifier = Modifier
.padding(
start = 8.dp,
top = 12.dp,
end = 8.dp,
bottom = 20.dp,
),
) {
Image(
painter = painterResource(id = R.drawable.img_tangem_cards_vertical),
contentDescription = null,
)
SecondaryButton(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter),
text = stringResourceSafe(R.string.details_buy_wallet),
onClick = state.onBuyClick,
)
}
}
}
}
}
@Composable
private fun FeatureItem(@DrawableRes iconResId: Int, text: TextReference) {
Row(
modifier = Modifier
.wrapContentWidth()
.padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.spacedBy(6.dp),
) {
Icon(
modifier = Modifier.size(16.dp),
painter = painterResource(iconResId),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
)
Text(
text = text.resolveReference(),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.secondary,
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
@ -256,7 +129,6 @@ private class WalletBackupUMProvider : CollectionPreviewParameterProvider<Wallet
),
googleDriveStatus = BackupStatus.ComingSoon,
onBackClick = {},
onBuyClick = {},
onRecoveryPhraseClick = {},
onGoogleDriveClick = {},
onHardwareWalletClick = {},
@ -277,7 +149,6 @@ private class WalletBackupUMProvider : CollectionPreviewParameterProvider<Wallet
),
googleDriveStatus = BackupStatus.NoBackup,
onBackClick = {},
onBuyClick = {},
onRecoveryPhraseClick = {},
onGoogleDriveClick = {},
onHardwareWalletClick = {},
@ -298,7 +169,23 @@ private class WalletBackupUMProvider : CollectionPreviewParameterProvider<Wallet
),
googleDriveStatus = BackupStatus.Done,
onBackClick = {},
onBuyClick = {},
onRecoveryPhraseClick = {},
onGoogleDriveClick = {},
onHardwareWalletClick = {},
backedUp = false,
),
WalletBackupUM(
hardwareWalletOption = null,
recoveryPhraseOption = LabelUM(
text = resourceReference(R.string.common_done),
style = LabelStyle.ACCENT,
),
googleDriveOption = LabelUM(
text = resourceReference(R.string.common_done),
style = LabelStyle.ACCENT,
),
googleDriveStatus = BackupStatus.Done,
onBackClick = {},
onRecoveryPhraseClick = {},
onGoogleDriveClick = {},
onHardwareWalletClick = {},

View file

@ -5,7 +5,6 @@ import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
internal data class WalletHardwareBackupUM(
val showPurchaseBlock: Boolean = false,
val blocks: ImmutableList<Block>,
val onBackClick: () -> Unit,
val onBuyClick: () -> Unit,

View file

@ -32,10 +32,8 @@ import com.tangem.features.hotwallet.wallethardwarebackup.entity.WalletHardwareB
import com.tangem.hot.sdk.model.HotWalletId
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
@ -119,7 +117,6 @@ internal class WalletHardwareBackupModel @Inject constructor(
init {
trackingContextProxy.addHotWalletContext()
analyticsEventHandler.send(WalletSettingsAnalyticEvents.HardwareBackupScreenOpened())
showPurchaseBlockWithDelay()
}
override fun onDestroy() {
@ -127,13 +124,6 @@ internal class WalletHardwareBackupModel @Inject constructor(
super.onDestroy()
}
private fun showPurchaseBlockWithDelay() {
modelScope.launch {
delay(SHOW_PURCHASE_BLOCK_DELAY)
uiState.update { it.copy(showPurchaseBlock = true) }
}
}
private fun onCreateNewWalletClick() {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonCreateNewWallet())
router.push(AppRoute.CreateHardwareWallet)
@ -175,8 +165,4 @@ internal class WalletHardwareBackupModel @Inject constructor(
.invoke(GenerateBuyTangemCardLinkUseCase.Source.Backup).let { urlOpener.openUrl(it) }
}
}
companion object {
private const val SHOW_PURCHASE_BLOCK_DELAY = 3000L
}
}

View file

@ -1,26 +1,26 @@
package com.tangem.features.hotwallet.wallethardwarebackup.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.SecondaryButton
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
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.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.ForceDarkTheme
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.hotwallet.common.ui.OptionBlock
@ -71,6 +71,8 @@ internal fun WalletHardwareBackupContent(state: WalletHardwareBackupUM, modifier
end = 16.dp,
),
) {
Banner(onBuyClick = state.onBuyClick)
state.blocks.forEach { block ->
OptionBlock(
modifier = Modifier
@ -86,45 +88,119 @@ internal fun WalletHardwareBackupContent(state: WalletHardwareBackupUM, modifier
)
}
}
AnimatedVisibility(state.showPurchaseBlock) {
PurchaseBlock(
onBuyClick = state.onBuyClick,
)
}
}
@Suppress("LongMethod")
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun Banner(onBuyClick: () -> Unit, modifier: Modifier = Modifier) {
ForceDarkTheme {
Column(
modifier = modifier
.background(
color = TangemTheme.colors.background.primary,
shape = RoundedCornerShape(16.dp),
),
) {
Column(
modifier = Modifier
.padding(
start = 12.dp,
top = 20.dp,
end = 12.dp,
),
) {
Text(
modifier = Modifier
.fillMaxWidth(),
text = stringResourceSafe(R.string.common_tangem_wallet),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
top = 4.dp,
),
text = stringResourceSafe(R.string.hw_backup_banner_description),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 24.dp,
top = 16.dp,
end = 24.dp,
),
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
FeatureItem(
iconResId = R.drawable.ic_shield_check_16,
text = resourceReference(R.string.welcome_create_wallet_feature_class),
)
FeatureItem(
iconResId = R.drawable.ic_flash_16,
text = resourceReference(R.string.welcome_create_wallet_feature_delivery),
)
FeatureItem(
iconResId = R.drawable.ic_sparkles_16,
text = resourceReference(R.string.welcome_create_wallet_feature_seedphrase),
)
}
Box(
modifier = Modifier
.padding(
start = 8.dp,
top = 12.dp,
end = 8.dp,
bottom = 20.dp,
),
) {
Image(
painter = painterResource(id = R.drawable.img_tangem_cards_vertical),
contentDescription = null,
)
SecondaryButton(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter),
text = stringResourceSafe(R.string.details_buy_wallet),
onClick = onBuyClick,
)
}
}
}
}
}
@Composable
private fun PurchaseBlock(onBuyClick: () -> Unit, modifier: Modifier = Modifier) {
private fun FeatureItem(@DrawableRes iconResId: Int, text: TextReference) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
.background(
color = TangemTheme.colors.background.primary,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.padding(
horizontal = 20.dp,
vertical = 16.dp,
),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.wrapContentWidth()
.padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.spacedBy(6.dp),
) {
Text(
modifier = Modifier
.weight(1f)
.padding(end = 16.dp),
text = stringResourceSafe(R.string.wallet_add_hardware_purchase),
style = TangemTheme.typography.button,
color = TangemTheme.colors.text.primary1,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
Icon(
modifier = Modifier.size(16.dp),
painter = painterResource(iconResId),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
)
SecondaryButton(
text = stringResourceSafe(R.string.wallet_import_buy_title),
onClick = onBuyClick,
size = TangemButtonSize.RoundedAction,
Text(
text = text.resolveReference(),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.secondary,
)
}
}
@ -156,7 +232,6 @@ private fun PreviewWalletHardwareBackupContent() {
onClick = { },
),
),
showPurchaseBlock = true,
onBuyClick = { },
),
)

View file

@ -65,7 +65,6 @@ internal class PreviewWalletSettingsComponent : WalletSettingsComponent {
onNotificationsDescriptionClick = {},
isNotificationsPermissionGranted = false,
onAccessCodeClick = {},
onUpgradeWalletClick = {},
onBackupClick = {},
onCardSettingsClick = {},
accountsUM = previewAccounts(),

View file

@ -31,6 +31,7 @@ internal sealed class WalletSettingsItemUM {
val text: TextReference,
val isEnabled: Boolean,
val imageState: ImageState,
val additionalBlock: BlockUM? = null,
val onClick: () -> Unit,
) : WalletSettingsItemUM()
@ -46,13 +47,6 @@ internal sealed class WalletSettingsItemUM {
val title: TextReference,
val description: TextReference,
) : WalletSettingsItemUM()
data class UpgradeWallet(
override val id: String,
val title: TextReference,
val description: TextReference,
val onClick: () -> Unit,
) : WalletSettingsItemUM()
}
@Immutable

View file

@ -96,7 +96,10 @@ internal class WalletSettingsModel @Inject constructor(
val params: WalletSettingsComponent.Params = paramsContainer.require()
val dialogNavigation = SlotNavigation<DialogConfig>()
val bottomSheetNavigation: SlotNavigation<NetworksAvailableForNotificationBSConfig> = SlotNavigation()
private val walletCardItemDelegate = walletCardItemDelegateFactory.create(dialogNavigation)
private val walletCardItemDelegate = walletCardItemDelegateFactory.create(
dialogNavigation = dialogNavigation,
onUpgradeHotWalletClick = ::onUpgradeWalletClick,
)
val state: MutableStateFlow<WalletSettingsUM> = MutableStateFlow(
value = WalletSettingsUM(
@ -249,7 +252,6 @@ internal class WalletSettingsModel @Inject constructor(
onCheckedNotificationsChanged = ::onCheckedNotificationsChange,
onNotificationsDescriptionClick = ::onNotificationsDescriptionClick,
onAccessCodeClick = { onAccessCodeClick(userWallet) },
onUpgradeWalletClick = { onUpgradeWalletClick() },
onBackupClick = ::onBackupClick,
onCardSettingsClick = ::onCardSettingsClick,
accountsUM = accountList,
@ -374,10 +376,7 @@ internal class WalletSettingsModel @Inject constructor(
val isCodeSet = userWallet.hotWalletId.authType != HotWalletId.AuthType.NoPassword
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonAccessCode(isCodeSet))
if (!state.value.isWalletBackedUp) {
showMakeBackupAtFirstAlertBS(
isUpgradeFlow = false,
action = WalletSettingsAnalyticEvents.NoticeBackupFirst.Action.AccessCode,
)
showMakeBackupAtFirstAlertBS()
} else {
unlockWalletIfNeedAndProceed { authorizationRequired ->
router.push(
@ -392,35 +391,28 @@ internal class WalletSettingsModel @Inject constructor(
}
private fun onUpgradeWalletClick() {
if (!state.value.isWalletBackedUp) {
showMakeBackupAtFirstAlertBS(
isUpgradeFlow = true,
action = WalletSettingsAnalyticEvents.NoticeBackupFirst.Action.Upgrade,
)
} else {
unlockWalletIfNeedAndProceed {
router.push(AppRoute.UpgradeWallet(userWalletId = params.userWalletId))
}
}
router.push(AppRoute.WalletHardwareBackup(userWalletId = params.userWalletId))
}
private fun onBackupClick() {
analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonBackup())
router.push(AppRoute.WalletBackup(params.userWalletId))
router.push(
AppRoute.WalletBackup(
userWalletId = params.userWalletId,
isColdWalletOptionShown = false,
),
)
}
private fun onCardSettingsClick() {
router.push(AppRoute.CardSettings(params.userWalletId))
}
private fun showMakeBackupAtFirstAlertBS(
action: WalletSettingsAnalyticEvents.NoticeBackupFirst.Action,
isUpgradeFlow: Boolean,
) {
private fun showMakeBackupAtFirstAlertBS() {
analyticsEventHandler.send(
event = WalletSettingsAnalyticEvents.NoticeBackupFirst(
source = AnalyticsParam.ScreensSources.WalletSettings.value,
action = action,
action = WalletSettingsAnalyticEvents.NoticeBackupFirst.Action.AccessCode,
),
)
val message = bottomSheetMessage {
@ -438,14 +430,10 @@ internal class WalletSettingsModel @Inject constructor(
router.push(
AppRoute.CreateWalletBackup(
userWalletId = params.userWalletId,
isUpgradeFlow = isUpgradeFlow,
isUpgradeFlow = false,
shouldSetAccessCode = true,
analyticsSource = AnalyticsParam.ScreensSources.WalletSettings.value,
analyticsAction = if (isUpgradeFlow) {
RecoveryPhraseScreenAction.Upgrade.value
} else {
RecoveryPhraseScreenAction.AccessCode.value
},
analyticsAction = RecoveryPhraseScreenAction.AccessCode.value,
),
)
closeBs()

View file

@ -42,7 +42,6 @@ import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -153,10 +152,6 @@ private fun Content(state: WalletSettingsUM, modifier: Modifier = Modifier) {
modifier = itemModifier,
model = item,
)
is WalletSettingsItemUM.UpgradeWallet -> UpgradeWalletBlock(
modifier = itemModifier,
model = item,
)
is WalletSettingsAccountsUM.Header -> AccountsHeader(item, itemModifier)
is WalletSettingsAccountsUM.Account -> AccountItem(
model = item,
@ -211,35 +206,54 @@ private fun CardBlock(model: WalletSettingsItemUM.CardBlock, modifier: Modifier
enabled = model.isEnabled,
onClick = model.onClick,
) {
Row(
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
verticalAlignment = Alignment.CenterVertically,
) {
CardImage(model.imageState)
Column(modifier = Modifier.weight(1f)) {
Text(
text = model.title.resolveReference(),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = model.text.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
Column {
Row(
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
verticalAlignment = Alignment.CenterVertically,
) {
CardImage(model.imageState)
Column(modifier = Modifier.weight(1f)) {
Text(
text = model.title.resolveReference(),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = model.text.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
}
SecondarySmallButton(
config = SmallButtonConfig(
isEnabled = model.isEnabled,
text = resourceReference(R.string.common_rename),
onClick = model.onClick,
),
)
}
SecondarySmallButton(
config = SmallButtonConfig(
isEnabled = model.isEnabled,
text = resourceReference(R.string.common_rename),
onClick = model.onClick,
),
)
if (model.additionalBlock != null) {
Column {
HorizontalDivider(
thickness = TangemTheme.dimens.size0_5,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing12),
color = TangemTheme.colors.stroke.primary,
)
BlockItem(
modifier = Modifier.fillMaxWidth(),
model = model.additionalBlock,
)
}
}
}
}
}
@ -272,21 +286,6 @@ private fun SwitchBlock(model: WalletSettingsItemUM.WithSwitch, modifier: Modifi
}
}
@Composable
private fun UpgradeWalletBlock(model: WalletSettingsItemUM.UpgradeWallet, modifier: Modifier = Modifier) {
Notification(
config = NotificationConfig(
title = model.title,
subtitle = model.description,
iconResId = R.drawable.ic_hardware_backup_36,
iconSize = 36.dp,
onClick = model.onClick,
shouldShowArrowIcon = false,
),
modifier = modifier,
)
}
@Composable
private fun NotificationAlertBlock(model: WalletSettingsItemUM.NotificationPermission, modifier: Modifier = Modifier) {
Notification(
@ -480,20 +479,4 @@ private fun Preview_WalletSettingsScreen() {
PreviewWalletSettingsComponent().Content(modifier = Modifier.fillMaxSize())
}
}
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun Preview_WalletSettingsScreen1() {
TangemThemePreview {
UpgradeWalletBlock(
model = WalletSettingsItemUM.UpgradeWallet(
id = "upgrade_wallet",
title = stringReference("Upgrade wallet with a hardware backup"),
description = stringReference("Keep your crypto safe with Tangems best-in-class hardware wallet."),
onClick = {},
),
)
}
}
// endregion Preview

View file

@ -38,17 +38,10 @@ internal class ItemsBuilder @Inject constructor() {
onReferralClick: () -> Unit,
onManageTokensClick: () -> Unit,
onAccessCodeClick: () -> Unit,
onUpgradeWalletClick: () -> Unit,
onBackupClick: () -> Unit,
onCardSettingsClick: () -> Unit,
): PersistentList<WalletSettingsItemUM> = persistentListOf<WalletSettingsItemUM>()
.add(cardItem)
.addAll(
buildUpgradeWalletItem(
userWallet = userWallet,
onUpgradeWalletClick = onUpgradeWalletClick,
),
)
.addAll(buildAccessCodeItem(userWallet, onAccessCodeClick))
.addAll(accountsUM)
.add(
@ -116,21 +109,6 @@ internal class ItemsBuilder @Inject constructor() {
onCheckedChange = onCheckedNFTChange,
)
private fun buildUpgradeWalletItem(
userWallet: UserWallet,
onUpgradeWalletClick: () -> Unit,
): List<WalletSettingsItemUM> = when (userWallet) {
is UserWallet.Cold -> emptyList()
is UserWallet.Hot -> listOf(
WalletSettingsItemUM.UpgradeWallet(
id = "upgrade_wallet",
title = resourceReference(id = R.string.hw_upgrade_to_cold_banner_title),
description = resourceReference(id = R.string.hw_upgrade_to_cold_banner_description),
onClick = onUpgradeWalletClick,
),
)
}
private fun buildNotificationsPermissionItem() = WalletSettingsItemUM.NotificationPermission(
id = "notifications_permission",
title = resourceReference(id = R.string.transaction_notifications_warning_title),

View file

@ -2,6 +2,7 @@ package com.tangem.feature.walletsettings.utils
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.tangem.core.ui.components.block.model.BlockUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.wallet.UserWallet
@ -24,6 +25,7 @@ internal class WalletCardItemDelegate @AssistedInject constructor(
private val getShouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase,
private val walletImageFetcher: UserWalletImageFetcher,
@Assisted private val dialogNavigation: SlotNavigation<DialogConfig>,
@Assisted private val onUpgradeHotWalletClick: () -> Unit,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) {
@ -43,10 +45,20 @@ internal class WalletCardItemDelegate @AssistedInject constructor(
isEnabled = isRenameAvailable,
onClick = { openRenameWalletDialog(wallet) },
imageState = imageState,
additionalBlock = buildUpgradeToHardwareWalletBlockOrNull(wallet),
)
},
)
private fun buildUpgradeToHardwareWalletBlockOrNull(wallet: UserWallet): BlockUM? {
return BlockUM(
text = resourceReference(R.string.upgrade_to_hardware_wallet_button_title),
iconRes = null,
onClick = onUpgradeHotWalletClick,
accentType = BlockUM.AccentType.ACCENT,
).takeIf { wallet is UserWallet.Hot }
}
private fun openRenameWalletDialog(userWallet: UserWallet) {
val config = DialogConfig.RenameWallet(
userWalletId = userWallet.walletId,
@ -57,6 +69,9 @@ internal class WalletCardItemDelegate @AssistedInject constructor(
@AssistedFactory
interface Factory {
fun create(dialogNavigation: SlotNavigation<DialogConfig>): WalletCardItemDelegate
fun create(
dialogNavigation: SlotNavigation<DialogConfig>,
onUpgradeHotWalletClick: () -> Unit,
): WalletCardItemDelegate
}
}

View file

@ -477,7 +477,16 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
override fun onFinishWalletActivationClick(isBackupExists: Boolean) {
analyticsEventHandler.send(MainScreen.ButtonFinalizeActivation())
val userWalletId = stateHolder.getSelectedWalletId()
appRouter.push(WalletActivation(userWalletId, isBackupExists))
val route = if (isBackupExists) {
WalletActivation(userWalletId = userWalletId, isBackupExists = true)
} else {
WalletBackup(
userWalletId = userWalletId,
isColdWalletOptionShown = true,
)
}
appRouter.push(route)
}
override fun onAllowPermissions() {

View file

@ -1,6 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.analytics.utils
import com.tangem.common.routing.AppRoute.WalletActivation
import com.tangem.common.routing.AppRoute.WalletBackup
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
@ -93,7 +93,12 @@ internal class WalletWarningsSingleEventSender @Inject constructor(
primaryButton {
text = resourceReference(R.string.hw_activation_need_backup)
onClick {
router.push(WalletActivation(userWallet.walletId, userWallet.backedUp))
router.push(
WalletBackup(
userWalletId = userWalletId,
isColdWalletOptionShown = true,
),
)
closeBs()
}
}

View file

@ -14,6 +14,7 @@ android {
dependencies {
implementation(projects.features.welcome.api)
implementation(projects.features.wallet.api)
implementation(projects.features.hotWallet.api)
/** Core */
implementation(projects.core.configToggles)

View file

@ -12,14 +12,22 @@ 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.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.SaveWalletError
import com.tangem.domain.common.wallets.error.UnlockWalletError
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.settings.CanUseBiometryUseCase
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.NonBiometricUnlockWalletUseCase
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.features.wallet.utils.UserWalletsFetcher
import com.tangem.features.welcome.impl.ui.state.WelcomeUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -40,10 +48,15 @@ internal class WelcomeModel @Inject constructor(
private val userWalletsListRepository: UserWalletsListRepository,
private val nonBiometricUnlockWalletUseCase: NonBiometricUnlockWalletUseCase,
private val canUseBiometryUseCase: CanUseBiometryUseCase,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val saveWalletUseCase: SaveWalletUseCase,
private val walletsRepository: WalletsRepository,
private val trackingContextProxy: TrackingContextProxy,
private val analyticsEventHandler: AnalyticsEventHandler,
userWalletsFetcherFactory: UserWalletsFetcher.Factory,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val scanCardProcessor: ScanCardProcessor,
private val messageSender: UiMessageSender,
) : Model() {
val uiState: StateFlow<WelcomeUM>
@ -168,7 +181,50 @@ internal class WelcomeModel @Inject constructor(
private fun addWalletClick() {
analyticsEventHandler.send(SignIn.ButtonAddWallet(AnalyticsParam.ScreensSources.SignIn))
router.push(AppRoute.CreateWalletSelection)
if (hotWalletFeatureToggles.isWalletCreationRestrictionEnabled) {
scanCard()
} else {
router.push(AppRoute.CreateWalletSelection)
}
}
private fun scanCard() {
modelScope.launch {
scanCardProcessor.scan(
analyticsSource = AnalyticsParam.ScreensSources.SignIn,
onWalletNotCreated = {},
disclaimerWillShow = { router.pop() },
onSuccess = { scanResponse ->
val userWallet =
coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build() ?: return@scan
saveWalletUseCase.invoke(userWallet)
.onLeft { error ->
if (error is SaveWalletError.WalletAlreadySaved) {
userWalletsListRepository.unlock(
userWallet.walletId,
unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse),
).onRight {
userWalletsListRepository.select(userWallet.walletId)
router.replaceAll(AppRoute.Wallet)
}
}
}
.onRight {
router.replaceAll(AppRoute.Wallet)
}
},
onCancel = {},
onFailure = { tangemError ->
if (!tangemError.silent) {
val message = tangemError.messageResId
?.let(::resourceReference)
?: stringReference(tangemError.customMessage)
messageSender.send(SnackbarMessage(message))
}
},
)
}
}
private suspend fun onlyOneHotWalletWithAccessCode(): Boolean {