Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-18 14:19:48 +05:00
parent cb5b1f6cba
commit 48b8c77f5d
10 changed files with 111 additions and 113 deletions

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="33dp"
android:viewportWidth="32"
android:viewportHeight="33">
<path
android:pathData="M16,4.062C17.587,4.062 19.084,4.339 20.489,4.893C21.894,5.447 23.151,6.22 24.259,7.212C25.366,8.204 26.272,9.361 26.974,10.684C27.677,11.998 28.119,13.425 28.301,14.962H30.26C30.5,14.962 30.682,15.012 30.806,15.111C30.93,15.21 30.988,15.342 30.98,15.508C30.971,15.665 30.901,15.843 30.769,16.041L28.066,19.885C27.892,20.133 27.694,20.257 27.47,20.257C27.255,20.249 27.065,20.125 26.9,19.885L24.197,16.029C24.064,15.839 23.994,15.665 23.986,15.508C23.986,15.342 24.048,15.21 24.172,15.111C24.296,15.012 24.474,14.962 24.705,14.962H26.677C26.495,13.648 26.094,12.428 25.474,11.304C24.862,10.172 24.077,9.184 23.118,8.34C22.159,7.497 21.072,6.84 19.857,6.369C18.65,5.898 17.364,5.662 16,5.662C14.405,5.662 12.904,5.988 11.499,6.642C10.101,7.286 8.903,8.171 7.903,9.295C7.712,9.502 7.518,9.614 7.32,9.63C7.121,9.638 6.944,9.585 6.786,9.469C6.613,9.345 6.505,9.167 6.464,8.936C6.423,8.696 6.497,8.469 6.687,8.254C7.836,6.956 9.217,5.935 10.829,5.191C12.441,4.438 14.165,4.062 16,4.062ZM16,28.938C14.413,28.938 12.916,28.656 11.511,28.094C10.106,27.54 8.849,26.767 7.741,25.775C6.634,24.792 5.724,23.642 5.013,22.328C4.311,21.005 3.868,19.575 3.686,18.038H1.727C1.487,18.038 1.31,17.988 1.194,17.889C1.07,17.789 1.012,17.657 1.02,17.492C1.02,17.327 1.086,17.149 1.219,16.959L3.922,13.102C4.096,12.862 4.29,12.743 4.505,12.743C4.728,12.743 4.926,12.862 5.1,13.102L7.803,16.971C7.936,17.161 8.002,17.339 8.002,17.504C8.002,17.661 7.94,17.789 7.816,17.889C7.7,17.988 7.522,18.038 7.283,18.038H5.323C5.497,19.344 5.894,20.563 6.514,21.696C7.134,22.82 7.919,23.808 8.87,24.659C9.829,25.503 10.912,26.16 12.119,26.631C13.334,27.102 14.628,27.338 16,27.338C17.587,27.338 19.079,27.011 20.476,26.358C21.882,25.713 23.089,24.825 24.097,23.692C24.288,23.494 24.478,23.39 24.668,23.382C24.866,23.366 25.044,23.411 25.201,23.518C25.375,23.651 25.482,23.837 25.524,24.076C25.565,24.308 25.49,24.531 25.3,24.746C24.16,26.036 22.779,27.057 21.159,27.809C19.546,28.561 17.827,28.938 16,28.938Z"
android:fillColor="#0099FF"/>
<path
android:pathData="M16.153,10.844C17.725,10.924 18.975,12.223 18.975,13.815V15.295C19.876,15.317 20.6,16.055 20.6,16.961V20.492C20.6,21.413 19.853,22.159 18.933,22.159H13.069C12.148,22.159 11.402,21.413 11.402,20.492V16.961C11.402,16.055 12.125,15.318 13.026,15.295V13.815C13.026,12.172 14.357,10.84 16,10.84L16.153,10.844ZM16,12.23C15.125,12.23 14.415,12.939 14.415,13.815V15.294H17.586V13.815C17.586,12.939 16.876,12.23 16,12.23Z"
android:fillColor="#0099FF"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M49.875,26.906V20.475C49.875,17.902 49.875,16.616 49.377,15.633C48.939,14.769 48.24,14.066 47.381,13.626C46.403,13.125 45.124,13.125 42.566,13.125H13.434C10.876,13.125 9.597,13.125 8.619,13.626C7.76,14.066 7.061,14.769 6.623,15.633C6.125,16.616 6.125,17.902 6.125,20.475V28.744C6.125,31.316 6.125,32.603 6.623,33.585C7.061,34.45 7.76,35.153 8.619,35.593C9.597,36.094 10.876,36.094 13.434,36.094H25.716M28,24.609H28.011M38.546,24.609H38.557M17.454,24.609H17.466M28.571,24.609C28.571,24.927 28.315,25.184 28,25.184C27.685,25.184 27.429,24.927 27.429,24.609C27.429,24.292 27.685,24.035 28,24.035C28.315,24.035 28.571,24.292 28.571,24.609ZM39.117,24.609C39.117,24.927 38.861,25.184 38.546,25.184C38.23,25.184 37.975,24.927 37.975,24.609C37.975,24.292 38.23,24.035 38.546,24.035C38.861,24.035 39.117,24.292 39.117,24.609ZM18.025,24.609C18.025,24.927 17.77,25.184 17.454,25.184C17.139,25.184 16.883,24.927 16.883,24.609C16.883,24.292 17.139,24.035 17.454,24.035C17.77,24.035 18.025,24.292 18.025,24.609Z"
android:strokeLineJoin="round"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#008EFF"
android:strokeLineCap="round"/>
<path
android:pathData="M45.242,38.664V34.645C45.242,32.425 43.452,30.625 41.244,30.625C39.037,30.625 37.247,32.425 37.247,34.645V38.664M36.905,47.852H45.584C46.864,47.852 47.503,47.852 47.992,47.601C48.422,47.381 48.771,47.03 48.99,46.597C49.239,46.106 49.239,45.463 49.239,44.177V42.339C49.239,41.053 49.239,40.41 48.99,39.918C48.771,39.486 48.422,39.135 47.992,38.914C47.503,38.664 46.864,38.664 45.584,38.664H36.905C35.625,38.664 34.986,38.664 34.497,38.914C34.067,39.135 33.718,39.486 33.499,39.918C33.25,40.41 33.25,41.053 33.25,42.339V44.177C33.25,45.463 33.25,46.106 33.499,46.597C33.718,47.03 34.067,47.381 34.497,47.601C34.986,47.852 35.625,47.852 36.905,47.852Z"
android:strokeLineJoin="round"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#008EFF"
android:strokeLineCap="round"/>
</vector>

View file

@ -1,7 +1,6 @@
package com.tangem.features.hotwallet.addexistingwallet.im.port.entity
import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
@ -18,6 +17,5 @@ internal data class AddExistingWalletImportUM(
val importWalletClick: () -> Unit,
val suggestionsList: ImmutableList<String>,
val onSuggestionClick: (String) -> Unit,
val infoBottomSheetConfig: TangemBottomSheetConfig,
val readyToImport: Boolean,
)

View file

@ -1,8 +1,18 @@
package com.tangem.features.hotwallet.addexistingwallet.im.port.model
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.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
import com.tangem.core.ui.components.bottomsheets.message.icon
import com.tangem.core.ui.components.bottomsheets.message.infoBlock
import com.tangem.core.ui.components.bottomsheets.message.onClick
import com.tangem.core.ui.components.bottomsheets.message.secondaryButton
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.bottomSheetMessage
import com.tangem.crypto.bip39.Mnemonic
import com.tangem.domain.wallets.builder.HotUserWalletBuilder
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
@ -19,6 +29,7 @@ import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@Suppress("LongParameterList")
@ModelScoped
internal class AddExistingWalletImportModel @Inject constructor(
paramsContainer: ParamsContainer,
@ -27,12 +38,29 @@ internal class AddExistingWalletImportModel @Inject constructor(
private val tangemHotSdk: TangemHotSdk,
private val hotUserWalletBuilderFactory: HotUserWalletBuilder.Factory,
private val saveUserWalletUseCase: SaveWalletUseCase,
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
) : Model() {
private val params: AddExistingWalletImportComponent.Params = paramsContainer.require()
private val importSeedPhraseUiStateBuilder: ImportSeedPhraseUiStateBuilder
private val passphraseInfoAlertBS
get() = bottomSheetMessage {
infoBlock {
icon(R.drawable.ic_passcode_lock_56) {
type = MessageBottomSheetUMV2.Icon.Type.Accent
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
}
title = resourceReference(R.string.common_passphrase)
body = resourceReference(R.string.onboarding_bottom_sheet_passphrase_description)
}
secondaryButton {
text = resourceReference(R.string.common_got_it)
onClick { closeBs() }
}
}
init {
importSeedPhraseUiStateBuilder = ImportSeedPhraseUiStateBuilder(
modelScope = modelScope,
@ -45,6 +73,7 @@ internal class AddExistingWalletImportModel @Inject constructor(
passphrase = passphrase,
)
},
onPassphraseInfoClick = ::onPassphraseInfoClick,
)
}
@ -73,4 +102,8 @@ internal class AddExistingWalletImportModel @Inject constructor(
}
}
}
private fun onPassphraseInfoClick() {
uiMessageSender.send(passphraseInfoAlertBS)
}
}

View file

@ -4,7 +4,6 @@ import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.common.core.TangemSdkError
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.crypto.bip39.Mnemonic
import com.tangem.crypto.bip39.MnemonicErrorResult
@ -24,6 +23,7 @@ internal class ImportSeedPhraseUiStateBuilder(
private val readyToImport: (Boolean) -> Unit,
private val updateUiState: ((AddExistingWalletImportUM) -> AddExistingWalletImportUM) -> Unit,
private val importWallet: (mnemonic: Mnemonic, passphrase: String?) -> Unit,
private val onPassphraseInfoClick: () -> Unit,
) {
private val wordsCheckJobHolder = JobHolder()
private var importedMnemonic: Mnemonic? = null
@ -49,11 +49,10 @@ internal class ImportSeedPhraseUiStateBuilder(
passphrase = it.text
updateUiState { state -> state.copy(passPhrase = it) }
},
onPassphraseInfoClick = ::showInfoBS,
onPassphraseInfoClick = onPassphraseInfoClick,
importWalletClick = ::onCreateWallet,
onSuggestionClick = { word -> addSuggestedWord(word) },
readyToImport = false,
infoBottomSheetConfig = TangemBottomSheetConfig.Empty,
)
}
@ -168,19 +167,6 @@ internal class ImportSeedPhraseUiStateBuilder(
}
}
private fun showInfoBS() {
updateUiState { state ->
state.copy(
infoBottomSheetConfig = TangemBottomSheetConfig.Companion.Empty.copy(
isShown = true,
onDismissRequest = {
updateUiState { it.copy(infoBottomSheetConfig = TangemBottomSheetConfig.Companion.Empty) }
},
),
)
}
}
companion object {
private const val MINIMUM_WORD_LENGTH = 2
private const val WORDS_INTERCEPT_DELAY_MS = 500L

View file

@ -37,7 +37,6 @@ import com.tangem.core.ui.components.Notifier
import com.tangem.core.ui.components.OutlineTextFieldWithIcon
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.TangemTextFieldsDefault
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.keyboardAsState
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.features.hotwallet.addexistingwallet.im.port.ui.utils.InvalidWordsColorTransformation
@ -113,8 +112,6 @@ internal fun AddExistingWalletImportContent(state: AddExistingWalletImportUM, mo
)
}
}
PassphraseInfoBottomSheet(state.infoBottomSheetConfig)
}
@Composable
@ -233,7 +230,6 @@ private fun PreviewAddExistingWalletImportContent() {
importWalletClick = {},
suggestionsList = persistentListOf(),
onSuggestionClick = {},
infoBottomSheetConfig = TangemBottomSheetConfig.Empty,
readyToImport = false,
),
)

View file

@ -1,90 +0,0 @@
package com.tangem.features.hotwallet.addexistingwallet.im.port.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
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 com.tangem.core.ui.R
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@Composable
fun PassphraseInfoBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet(
config = config,
containerColor = TangemTheme.colors.background.primary,
) { _: TangemBottomSheetConfigContent.Empty ->
PassphraseInfoBottomSheetContent(config.onDismissRequest)
}
}
@Composable
fun PassphraseInfoBottomSheetContent(onDismiss: () -> Unit) {
Column(
modifier = Modifier
.background(color = TangemTheme.colors.background.primary)
.fillMaxWidth(),
) {
Icon(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(top = TangemTheme.dimens.size40)
.size(TangemTheme.dimens.size48),
painter = painterResource(id = R.drawable.ic_information_24),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
)
Text(
text = stringResourceSafe(id = R.string.common_passphrase),
modifier = Modifier
.padding(top = TangemTheme.dimens.size40)
.align(Alignment.CenterHorizontally),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
)
Text(
text = stringResourceSafe(id = R.string.onboarding_bottom_sheet_passphrase_description),
modifier = Modifier
.padding(top = TangemTheme.dimens.size16)
.padding(horizontal = TangemTheme.dimens.size24)
.align(Alignment.CenterHorizontally),
color = TangemTheme.colors.text.secondary,
style = TangemTheme.typography.body2,
textAlign = TextAlign.Center,
)
PrimaryButton(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.size16)
.padding(top = TangemTheme.dimens.size40)
.padding(bottom = TangemTheme.dimens.size32)
.fillMaxWidth(),
text = stringResourceSafe(id = R.string.common_ok),
onClick = onDismiss,
)
}
}
@Preview
@Composable
private fun PassphraseInfoBottomSheetContentPreview() {
TangemThemePreview {
PassphraseInfoBottomSheetContent({ })
}
}

View file

@ -8,6 +8,7 @@ internal data class WalletBackupUM(
val googleDriveStatus: LabelUM?,
val onRecoveryPhraseClick: () -> Unit,
val onGoogleDriveClick: () -> Unit,
val backedUp: Boolean,
)
internal sealed class BackupStatus {

View file

@ -1,13 +1,21 @@
package com.tangem.features.hotwallet.walletbackup.model
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.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
import com.tangem.core.ui.components.bottomsheets.message.icon
import com.tangem.core.ui.components.bottomsheets.message.infoBlock
import com.tangem.core.ui.components.bottomsheets.message.onClick
import com.tangem.core.ui.components.bottomsheets.message.secondaryButton
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.bottomSheetMessage
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.hotwallet.WalletBackupComponent
@ -22,6 +30,7 @@ internal class WalletBackupModel @Inject constructor(
getWalletUseCase: GetUserWalletUseCase,
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider,
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
) : Model() {
private val params: WalletBackupComponent.Params = paramsContainer.require()
@ -38,11 +47,31 @@ internal class WalletBackupModel @Inject constructor(
text = resourceReference(R.string.common_coming_soon),
style = LabelStyle.REGULAR,
),
onRecoveryPhraseClick = { },
onRecoveryPhraseClick = ::onRecoveryPhraseClick,
onGoogleDriveClick = { },
backedUp = false,
),
)
private val makeBackupAtFirstAlertBS
get() = bottomSheetMessage {
infoBlock {
icon(R.drawable.ic_passcode_lock_32) {
type = MessageBottomSheetUMV2.Icon.Type.Accent
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
}
title = resourceReference(R.string.hw_backup_need_title)
body = resourceReference(R.string.hw_backup_need_description)
}
secondaryButton {
text = resourceReference(R.string.hw_backup_need_action)
onClick {
closeBs()
// TODO [REDACTED_TASK_KEY]
}
}
}
init {
getWalletUseCase.invokeFlow(params.userWalletId)
.map { it.getOrNull() }
@ -80,5 +109,14 @@ internal class WalletBackupModel @Inject constructor(
text = resourceReference(R.string.common_coming_soon),
style = LabelStyle.REGULAR,
),
backedUp = userWallet.backedUp,
)
private fun onRecoveryPhraseClick() {
if (uiState.value.backedUp) {
// TODO [REDACTED_TASK_KEY]
} else {
uiMessageSender.send(makeBackupAtFirstAlertBS)
}
}
}

View file

@ -96,6 +96,7 @@ private class WalletBackupUMProvider : CollectionPreviewParameterProvider<Wallet
onBackClick = {},
onRecoveryPhraseClick = {},
onGoogleDriveClick = {},
backedUp = false,
),
WalletBackupUM(
recoveryPhraseStatus = LabelUM(
@ -109,6 +110,7 @@ private class WalletBackupUMProvider : CollectionPreviewParameterProvider<Wallet
onBackClick = {},
onRecoveryPhraseClick = {},
onGoogleDriveClick = {},
backedUp = false,
),
WalletBackupUM(
recoveryPhraseStatus = LabelUM(
@ -122,6 +124,7 @@ private class WalletBackupUMProvider : CollectionPreviewParameterProvider<Wallet
onBackClick = {},
onRecoveryPhraseClick = {},
onGoogleDriveClick = {},
backedUp = false,
),
),
)