Updated on 2026-08-14
This commit is contained in:
parent
c7458d8ab0
commit
35e3ff8ad1
24 changed files with 254 additions and 67 deletions
|
|
@ -25,6 +25,7 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPay
|
|||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AddMoreBackupCardsDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInProgressDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ResetBackupCardDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendBottomSheetDialog
|
||||
|
|
@ -131,6 +132,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
||||
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context)
|
||||
is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context)
|
||||
is BackupDialog.ResetBackupCard -> ResetBackupCardDialog.create(context)
|
||||
is SaltPayDialog.Activation.NoGas -> NoFundsForActivationDialog.create(context)
|
||||
is SaltPayDialog.Activation.PutVisaCard -> PutVisaCardDialog.create(context)
|
||||
is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog)
|
||||
|
|
|
|||
|
|
@ -5,22 +5,14 @@ import androidx.annotation.StringRes
|
|||
import com.tangem.Message
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CardFilter
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.SuccessResponse
|
||||
import com.tangem.common.UserCode
|
||||
import com.tangem.common.UserCodeType
|
||||
import com.tangem.common.*
|
||||
import com.tangem.common.biometric.BiometricManager
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.Config
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.core.UserCodeRequestPolicy
|
||||
import com.tangem.common.core.*
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.map
|
||||
import com.tangem.common.usersCode.UserCodeRepository
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.crypto.bip39.DefaultMnemonic
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
|
@ -45,6 +37,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
|
|||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
|
||||
|
||||
private val userCodeRepository by lazy {
|
||||
|
|
@ -91,6 +84,21 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun importWallet(
|
||||
scanResponse: ScanResponse,
|
||||
mnemonic: String,
|
||||
): CompletionResult<CreateProductWalletTaskResponse> {
|
||||
return when (val seedResult = DefaultMnemonic(mnemonic, tangemSdk.wordlist).generateSeed()) {
|
||||
is CompletionResult.Success -> runTaskAsync(
|
||||
CreateProductWalletTask(scanResponse.cardTypesResolver, seedResult.data),
|
||||
scanResponse.card.cardId,
|
||||
Message(context.getString(R.string.initial_message_create_wallet_body)),
|
||||
)
|
||||
|
||||
is CompletionResult.Failure -> CompletionResult.Failure(seedResult.error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendScanResultsToAnalytics(result: CompletionResult<ScanResponse>) {
|
||||
if (result is CompletionResult.Failure) {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
|
|
@ -115,7 +123,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId)
|
||||
}
|
||||
|
||||
suspend fun resetToFactorySettings(cardId: String): CompletionResult<CardDTO> {
|
||||
suspend fun resetToFactorySettings(cardId: String? = null): CompletionResult<CardDTO> {
|
||||
return runTaskAsyncReturnOnMain(
|
||||
runnable = ResetToFactorySettingsTask(),
|
||||
cardId = cardId,
|
||||
|
|
@ -249,7 +257,6 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
allowUntrustedCards = true,
|
||||
filter = CardFilter(
|
||||
allowedCardTypes = FirmwareVersion.FirmwareType.values().toList(),
|
||||
maxFirmwareVersion = FirmwareVersion(major = 4, minor = 52),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ private data class CreateWalletResponse(
|
|||
|
||||
class CreateProductWalletTask(
|
||||
private val cardTypesResolver: CardTypesResolver,
|
||||
private val seed: ByteArray? = null,
|
||||
) : CardSessionRunnable<CreateProductWalletTaskResponse> {
|
||||
|
||||
override val allowsRequestAccessCodeFromRepository: Boolean = false
|
||||
|
|
@ -74,7 +75,8 @@ class CreateProductWalletTask(
|
|||
cardTypesResolver.isTangemNote() -> CreateWalletTangemNote(cardTypesResolver)
|
||||
cardTypesResolver.isTangemTwins() ->
|
||||
throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet")
|
||||
else -> CreateWalletTangemWallet()
|
||||
|
||||
else -> CreateWalletTangemWallet(seed)
|
||||
}
|
||||
commandProcessor.proceed(cardDto, session) {
|
||||
when (it) {
|
||||
|
|
@ -129,7 +131,9 @@ private class CreateWalletTangemNote(private val cardTypesResolver: CardTypesRes
|
|||
}
|
||||
}
|
||||
|
||||
private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWalletTaskResponse> {
|
||||
private class CreateWalletTangemWallet(
|
||||
private val seed: ByteArray?,
|
||||
) : ProductCommandProcessor<CreateProductWalletTaskResponse> {
|
||||
|
||||
private var primaryCard: PrimaryCard? = null
|
||||
|
||||
|
|
@ -149,7 +153,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
return
|
||||
}
|
||||
|
||||
CreateWalletsTask(curves).run(session) { result ->
|
||||
CreateWalletsTask(curves, seed).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
proceedWithCreatedWallets(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class CreateWalletsResponse(
|
|||
|
||||
class CreateWalletsTask(
|
||||
private val curves: List<EllipticCurve>,
|
||||
private val seed: ByteArray? = null,
|
||||
) : CardSessionRunnable<CreateWalletsResponse> {
|
||||
|
||||
private val createdWalletsResponses = mutableListOf<CreateWalletResponse>()
|
||||
|
|
@ -37,7 +38,7 @@ class CreateWalletsTask(
|
|||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletsResponse>) -> Unit,
|
||||
) {
|
||||
CreateWalletTask(curve).run(session) { result ->
|
||||
CreateWalletTask(curve, seed).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
createdWalletsResponses.add(result.data)
|
||||
|
|
@ -47,6 +48,7 @@ class CreateWalletsTask(
|
|||
}
|
||||
createWallet(curves[createdWalletsResponses.size], session, callback)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,14 @@ object OnboardingHelper {
|
|||
cardInfoStorage.isActivationInProgress(cardId)
|
||||
}
|
||||
}
|
||||
|
||||
response.cardTypesResolver.isWallet2() -> {
|
||||
val emptyWallets = response.card.wallets.isEmpty()
|
||||
val activationInProgress = cardInfoStorage.isActivationInProgress(cardId)
|
||||
val backupNotActive = response.card.backupStatus?.isActive != true
|
||||
emptyWallets || activationInProgress || backupNotActive
|
||||
}
|
||||
|
||||
response.card.wallets.isNotEmpty() -> cardInfoStorage.isActivationInProgress(cardId)
|
||||
else -> true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,4 +88,6 @@ sealed class BackupAction : Action {
|
|||
object DiscardBackup : BackupAction()
|
||||
object DiscardSavedBackup : BackupAction()
|
||||
object ResumeFoundUnfinishedBackup : BackupAction()
|
||||
|
||||
object ResetBackupCard : BackupAction()
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
|
|||
import android.net.Uri
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.TapWorkarounds.isSaltPay
|
||||
|
|
@ -195,6 +196,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun handleWallet2Action(action: OnboardingWallet2Action, state: () -> AppState?) {
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager
|
||||
|
|
@ -207,6 +209,7 @@ private fun handleWallet2Action(action: OnboardingWallet2Action, state: () -> Ap
|
|||
store.dispatch(OnboardingWallet2Action.SetDependencies(action.maxProgress))
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.CreateWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
|
|
@ -219,6 +222,7 @@ private fun handleWallet2Action(action: OnboardingWallet2Action, state: () -> Ap
|
|||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
|
|
@ -228,9 +232,35 @@ private fun handleWallet2Action(action: OnboardingWallet2Action, state: () -> Ap
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.ImportWallet -> {
|
||||
// TODO: implement
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
val mediateResult = when (
|
||||
val result = tangemSdkManager.importWallet(
|
||||
scanResponse = scanResponse,
|
||||
mnemonic = action.mnemonicComponents.joinToString(" "),
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = CreateWalletResponse(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
withMainContext {
|
||||
action.callback(mediateResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.WalletWasCreated -> {
|
||||
val result = when (val mediateResult = action.result) {
|
||||
is CompletionResult.Success -> {
|
||||
|
|
@ -241,12 +271,14 @@ private fun handleWallet2Action(action: OnboardingWallet2Action, state: () -> Ap
|
|||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(mediateResult.error)
|
||||
}
|
||||
}
|
||||
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(result))
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
|
@ -320,7 +352,18 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
is CompletionResult.Success -> {
|
||||
store.dispatchOnMain(BackupAction.AddBackupCard.Success)
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.BackupFailedNotEmptyWallets &&
|
||||
onboardingWalletState.wallet2State != null
|
||||
) {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
BackupDialog.ResetBackupCard,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -425,6 +468,11 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
Analytics.send(Onboarding.Finished())
|
||||
finishCardActivation(notActivatedCardIds)
|
||||
}
|
||||
|
||||
is BackupAction.ResetBackupCard -> {
|
||||
scope.launch { tangemSdkManager.resetToFactorySettings() }
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,4 +122,5 @@ sealed class BackupDialog : StateDialog {
|
|||
object BackupInProgress : BackupDialog()
|
||||
object UnfinishedBackupFound : BackupDialog()
|
||||
object ConfirmDiscardingBackup : BackupDialog()
|
||||
object ResetBackupCard : BackupDialog()
|
||||
}
|
||||
|
|
@ -28,17 +28,28 @@ import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseVi
|
|||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.PropertyCalculator
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.inflate
|
||||
import com.tangem.tap.common.extensions.resourceUri
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.feedback.SupportInfo
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.BaseFragment
|
||||
import com.tangem.tap.features.FragmentOnBackPressedHandler
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.*
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWallet2Action
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.ui.OnboardingSaltPayStateHandler
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding
|
||||
|
|
@ -46,6 +57,7 @@ import com.tangem.wallet.databinding.LayoutOnboardingSaltpayBinding
|
|||
import com.tangem.wallet.databinding.LayoutOnboardingSeedPhraseBinding
|
||||
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@Suppress("LargeClass", "MagicNumber")
|
||||
|
|
@ -113,7 +125,7 @@ class OnboardingWalletFragment :
|
|||
)
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider? = OnboardingMenuProvider()
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider()
|
||||
|
||||
private fun reInitCardsWidgetIfNeeded(backupCardsCounts: Int) = with(binding) {
|
||||
val viewBackupCount = flCardsContainer.childCount - 1
|
||||
|
|
@ -202,7 +214,12 @@ class OnboardingWalletFragment :
|
|||
internal fun handleOnboardingStep(state: OnboardingWalletState) {
|
||||
when (state.step) {
|
||||
OnboardingWalletStep.CreateWallet -> setupCreateWalletState()
|
||||
OnboardingWalletStep.Backup -> setBackupState(state.backupState, state.isSaltPay)
|
||||
OnboardingWalletStep.Backup -> setBackupState(
|
||||
state = state.backupState,
|
||||
isSaltPay = state.isSaltPay,
|
||||
isWallet2 = state.wallet2State != null,
|
||||
)
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
|
@ -223,9 +240,9 @@ class OnboardingWalletFragment :
|
|||
animator.setupCreateWalletState()
|
||||
}
|
||||
|
||||
private fun setBackupState(state: BackupState, isSaltPay: Boolean) {
|
||||
private fun setBackupState(state: BackupState, isSaltPay: Boolean, isWallet2: Boolean) {
|
||||
when (state.backupStep) {
|
||||
BackupStep.InitBackup -> showBackupIntro(state, isSaltPay)
|
||||
BackupStep.InitBackup -> showBackupIntro(state, isSaltPay, isWallet2)
|
||||
BackupStep.ScanOriginCard -> showScanOriginCard()
|
||||
BackupStep.AddBackupCards -> showAddBackupCards(state, isSaltPay)
|
||||
BackupStep.SetAccessCode -> showSetAccessCode()
|
||||
|
|
@ -237,7 +254,7 @@ class OnboardingWalletFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun showBackupIntro(state: BackupState, isSaltPay: Boolean) = with(binding) {
|
||||
private fun showBackupIntro(state: BackupState, isSaltPay: Boolean, isWallet2: Boolean) = with(binding) {
|
||||
imvFirstBackupCard.show()
|
||||
imvSecondBackupCard.show()
|
||||
|
||||
|
|
@ -252,7 +269,7 @@ class OnboardingWalletFragment :
|
|||
|
||||
btnWalletAlternativeAction.text = getText(R.string.onboarding_button_skip_backup)
|
||||
btnWalletAlternativeAction.setOnClickListener { store.dispatch(BackupAction.SkipBackup) }
|
||||
btnWalletAlternativeAction.show(state.canSkipBackup && !isSaltPay)
|
||||
btnWalletAlternativeAction.show(state.canSkipBackup && !isSaltPay && !isWallet2)
|
||||
}
|
||||
animator.showBackupIntro(state)
|
||||
}
|
||||
|
|
@ -492,6 +509,19 @@ class OnboardingWalletFragment :
|
|||
) {
|
||||
store.dispatch(OnboardingWallet2Action.ImportWallet(mnemonicComponents, callback))
|
||||
}
|
||||
|
||||
override fun allowScreenshots(allow: Boolean) {
|
||||
mainScope.launch {
|
||||
if (allow) {
|
||||
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
} else {
|
||||
requireActivity().window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object ResetBackupCardDialog {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.common_warning)
|
||||
setMessage(R.string.onboarding_linking_error_card_with_wallets)
|
||||
setPositiveButton(R.string.common_continue) { _, _ ->
|
||||
store.dispatch(BackupAction.ResetBackupCard)
|
||||
}
|
||||
setNegativeButton(R.string.common_cancel) { _, _ ->
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:progressBackgroundTint="#401ACE80"
|
||||
android:progressTint="@color/accent" />
|
||||
android:progressBackgroundTint="#32000000"
|
||||
android:progressTint="@color/background_action" />
|
||||
</merge>
|
||||
|
|
|
|||
|
|
@ -45,12 +45,11 @@
|
|||
|
||||
<color name="all_colors_azure">#007AFF</color>
|
||||
|
||||
|
||||
<color name="background_action">#000000</color>
|
||||
<color name="background_primary">#FFFFFF</color>
|
||||
<color name="background_secondary">#F5F5F5</color>
|
||||
|
||||
|
||||
|
||||
<color name="button_disabled">#EBEBEB</color>
|
||||
<color name="button_primary">#1E1E1E</color>
|
||||
<color name="button_secondary">#EBEBEB</color>
|
||||
|
|
|
|||
|
|
@ -184,6 +184,7 @@
|
|||
<string name="onboarding_exit_alert_message">В этом случае вам будет необходимо начать процесс заново.</string>
|
||||
<string name="onboarding_exit_alert_title">Вы хотите выйти из процесса активации?</string>
|
||||
<string name="onboarding_getting_started">Подготовка</string>
|
||||
<string name="onboarding_linking_error_card_with_wallets">Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Хотите сбросить его и использовать карту для бэкапа?</string>
|
||||
<string name="onboarding_navbar_kyc_progress">Подтвердите свою личность</string>
|
||||
<string name="onboarding_navbar_kyc_start">Верификация клиента</string>
|
||||
<string name="onboarding_navbar_pin">Код доступа</string>
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@
|
|||
<string name="onboarding_exit_alert_message">In this case, you will need to start from the beginning.</string>
|
||||
<string name="onboarding_exit_alert_title">Do you want to exit the activation process?</string>
|
||||
<string name="onboarding_getting_started">Getting started</string>
|
||||
<string name="onboarding_linking_error_card_with_wallets">Another wallet has already been created on the card you\'re trying to add. Do you want to reset it and use the card for a new wallet?</string>
|
||||
<string name="onboarding_navbar_kyc_progress">Verify your identity</string>
|
||||
<string name="onboarding_navbar_kyc_start">KYC</string>
|
||||
<string name="onboarding_navbar_pin">Pin code</string>
|
||||
|
|
|
|||
|
|
@ -26,8 +26,9 @@ class TangemCardTypesResolver(
|
|||
card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable &&
|
||||
!card.isSaltPay
|
||||
|
||||
// override fun isWallet2(): Boolean = card.firmwareVersion >= FirmwareVersion.KeysImportAvailable
|
||||
override fun isWallet2(): Boolean = true
|
||||
override fun isWallet2(): Boolean =
|
||||
card.firmwareVersion >= FirmwareVersion.KeysImportAvailable && card.settings.isKeysImportAllowed
|
||||
|
||||
override fun isSaltPay(): Boolean = productType == ProductType.SaltPay
|
||||
override fun isSaltPayVisa(): Boolean = card.isSaltPayVisa
|
||||
override fun isSaltPayWallet(): Boolean = card.isSaltPayWallet
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.tangem.common.card.EncryptionMode
|
|||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.operations.attestation.Attestation
|
||||
import java.util.Date
|
||||
import java.util.*
|
||||
import com.tangem.common.card.FirmwareVersion as SdkFirmwareVersion
|
||||
|
||||
/**
|
||||
|
|
@ -103,6 +103,7 @@ data class CardDTO(
|
|||
val supportedEncryptionModes: List<EncryptionMode>,
|
||||
val isFilesAllowed: Boolean,
|
||||
val isHDWalletAllowed: Boolean,
|
||||
val isKeysImportAllowed: Boolean,
|
||||
) {
|
||||
constructor(settings: Card.Settings) : this(
|
||||
securityDelay = settings.securityDelay,
|
||||
|
|
@ -115,6 +116,7 @@ data class CardDTO(
|
|||
supportedEncryptionModes = settings.supportedEncryptionModes,
|
||||
isFilesAllowed = settings.isFilesAllowed,
|
||||
isHDWalletAllowed = settings.isHDWalletAllowed,
|
||||
isKeysImportAllowed = settings.isKeysImportAllowed,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@ dependencies {
|
|||
implementation(project(":core:ui"))
|
||||
implementation(project(":core:res"))
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.tangem.card.android) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
implementation(deps.androidx.appCompat)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,21 @@
|
|||
package com.tangem.feature.onboarding.api
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.*
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.AboutSeedPhraseScreen
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.CheckSeedPhraseScreen
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.ImportSeedPhraseScreen
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.IntroScreen
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.YourSeedPhraseScreen
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseScreen
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel
|
||||
|
||||
|
|
@ -20,7 +29,7 @@ class OnboardingSeedPhrase : OnboardingSeedPhraseApi {
|
|||
BackHandler(onBack = viewModel.uiState.onBackClick)
|
||||
TangemTheme {
|
||||
Column {
|
||||
ProgressIndicator(viewModel.progress, maxProgress)
|
||||
ProgressIndicator(viewModel.progress.collectAsState(0).value, maxProgress)
|
||||
Content(viewModel)
|
||||
}
|
||||
}
|
||||
|
|
@ -48,12 +57,13 @@ private fun ProgressIndicator(progress: Int, maxProgress: Int) {
|
|||
|
||||
@Composable
|
||||
private fun Content(viewModel: SeedPhraseViewModel) {
|
||||
when (viewModel.currentScreen) {
|
||||
when (viewModel.currentScreen.collectAsState().value) {
|
||||
SeedPhraseScreen.Intro -> {
|
||||
IntroScreen(
|
||||
state = viewModel.uiState.introState,
|
||||
)
|
||||
}
|
||||
|
||||
SeedPhraseScreen.AboutSeedPhrase -> {
|
||||
AboutSeedPhraseScreen(
|
||||
state = viewModel.uiState.aboutState,
|
||||
|
|
|
|||
|
|
@ -1,21 +1,17 @@
|
|||
package com.tangem.feature.onboarding.data
|
||||
|
||||
import com.tangem.crypto.bip39.*
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.crypto.bip39.DefaultMnemonic
|
||||
import com.tangem.crypto.bip39.EntropyLength
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.crypto.bip39.Wordlist
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultMnemonicRepository(
|
||||
private val assetReader: AssetReader,
|
||||
private val bip39Wordlist: Wordlist,
|
||||
) : MnemonicRepository {
|
||||
|
||||
// TODO: change initialization after adding it to the sdk
|
||||
private val bip39Wordlist: Wordlist by lazy {
|
||||
assetReader.openFile(MNEMONIC_FILE_NAME)
|
||||
.use { BIP39Wordlist(it) }
|
||||
}
|
||||
|
||||
private val bip39Words: Set<String> by lazy { bip39Wordlist.words.toHashSet() }
|
||||
|
||||
override fun getWordsDictionary(): Set<String> = bip39Words
|
||||
|
|
@ -25,8 +21,4 @@ internal class DefaultMnemonicRepository(
|
|||
}
|
||||
|
||||
override fun createMnemonic(mnemonicString: String): Mnemonic = DefaultMnemonic(mnemonicString, bip39Wordlist)
|
||||
|
||||
companion object {
|
||||
private const val MNEMONIC_FILE_NAME = "mnemonic/mnemonic_dictionary_en.txt"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.feature.onboarding.data.di
|
||||
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import android.app.Application
|
||||
import com.tangem.crypto.bip39.Wordlist
|
||||
import com.tangem.feature.onboarding.data.DefaultMnemonicRepository
|
||||
import com.tangem.feature.onboarding.data.MnemonicRepository
|
||||
import com.tangem.sdk.extensions.getWordlist
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -15,7 +17,7 @@ class OnboardingDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSeedPhraseSdkRepository(assetReader: AssetReader): MnemonicRepository {
|
||||
return DefaultMnemonicRepository(assetReader)
|
||||
fun provideSeedPhraseSdkRepository(application: Application): MnemonicRepository {
|
||||
return DefaultMnemonicRepository(Wordlist.getWordlist(application))
|
||||
}
|
||||
}
|
||||
|
|
@ -93,7 +93,6 @@ private fun PhraseBlock(state: ImportSeedPhraseState, modifier: Modifier = Modif
|
|||
),
|
||||
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
)
|
||||
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -11,4 +11,6 @@ interface SeedPhraseMediator {
|
|||
fun importWallet(mnemonicComponents: List<String>, callback: (CompletionResult<CreateWalletResponse>) -> Unit)
|
||||
|
||||
fun onWalletCreated(result: CompletionResult<CreateWalletResponse>)
|
||||
|
||||
fun allowScreenshots(allow: Boolean)
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import android.net.Uri
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -14,15 +16,21 @@ class SeedPhraseRouter(
|
|||
private val onOpenUriClick: (Uri) -> Unit,
|
||||
) {
|
||||
|
||||
var currentScreen by mutableStateOf(SeedPhraseScreen.Intro)
|
||||
private val _currentScreen = MutableStateFlow(SeedPhraseScreen.Intro)
|
||||
|
||||
val currentScreen: StateFlow<SeedPhraseScreen>
|
||||
get() = _currentScreen
|
||||
|
||||
var currentScreenState by mutableStateOf(SeedPhraseScreen.Intro)
|
||||
private set
|
||||
|
||||
fun navigateBack() {
|
||||
currentScreen = when (currentScreen) {
|
||||
_currentScreen.value = when (_currentScreen.value) {
|
||||
SeedPhraseScreen.Intro -> {
|
||||
onBack.invoke()
|
||||
return
|
||||
}
|
||||
|
||||
SeedPhraseScreen.AboutSeedPhrase -> SeedPhraseScreen.Intro
|
||||
SeedPhraseScreen.YourSeedPhrase -> SeedPhraseScreen.AboutSeedPhrase
|
||||
SeedPhraseScreen.CheckSeedPhrase -> SeedPhraseScreen.YourSeedPhrase
|
||||
|
|
@ -31,7 +39,7 @@ class SeedPhraseRouter(
|
|||
}
|
||||
|
||||
fun openScreen(screen: SeedPhraseScreen) {
|
||||
currentScreen = screen
|
||||
_currentScreen.value = screen
|
||||
}
|
||||
|
||||
fun openChat() {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,17 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.domain.SeedPhraseError
|
||||
import com.tangem.feature.onboarding.domain.SeedPhraseInteractor
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.*
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.AboutUiAction
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseUiAction
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhraseUiAction
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.IntroUiAction
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.MnemonicGridItem
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.SeedPhraseField
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldUiAction
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.UiActions
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.YourSeedPhraseUiAction
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders.StateBuilder
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.Debouncer
|
||||
|
|
@ -19,7 +29,16 @@ import com.tangem.utils.extensions.isEven
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -40,11 +59,11 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
private lateinit var router: SeedPhraseRouter
|
||||
private lateinit var mediator: SeedPhraseMediator
|
||||
|
||||
val currentScreen: SeedPhraseScreen
|
||||
val currentScreen: StateFlow<SeedPhraseScreen>
|
||||
get() = router.currentScreen
|
||||
|
||||
val progress: Int
|
||||
get() = progressByScreen[currentScreen] ?: 0
|
||||
val progress: Flow<Int>
|
||||
get() = currentScreen.map { progressByScreen[it] ?: 0 }
|
||||
|
||||
val maxProgress: Int by lazy { progressByScreen.maxOfOrNull { it.value } ?: 0 }
|
||||
|
||||
|
|
@ -150,8 +169,8 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
val isSameText = textFieldValue.text == oldTextFieldValue.text
|
||||
val isCursorMoved = textFieldValue.selection != oldTextFieldValue.selection
|
||||
|
||||
val mediateState = uiBuilder.importSeedPhrase.updateTextField(uiState, textFieldValue)
|
||||
uiState = uiBuilder.importSeedPhrase.updateCreateWalletButton(mediateState, enabled = false)
|
||||
uiState = uiBuilder.importSeedPhrase.updateTextField(uiState, textFieldValue)
|
||||
// uiState = uiBuilder.importSeedPhrase.updateCreateWalletButton(mediateState, enabled = false)
|
||||
|
||||
val fieldState = uiState.importSeedPhraseState.tvSeedPhrase
|
||||
val inputMnemonic = fieldState.textFieldValue.text
|
||||
|
|
@ -182,16 +201,21 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
|
||||
private suspend fun validateMnemonic(inputMnemonic: String) {
|
||||
if (inputMnemonic.isEmpty() && uiState.importSeedPhraseState.error != null) {
|
||||
updateUi { uiBuilder.importSeedPhrase.updateError(uiState, null) }
|
||||
updateUi {
|
||||
uiState = uiBuilder.importSeedPhrase.updateCreateWalletButton(uiState, enabled = false)
|
||||
uiBuilder.importSeedPhrase.updateError(uiState, null)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
interactor.validateMnemonicString(inputMnemonic)
|
||||
.onSuccess { mnemonicComponents ->
|
||||
importedMnemonicComponents = mnemonicComponents
|
||||
uiState = uiBuilder.importSeedPhrase.updateCreateWalletButton(uiState, enabled = true)
|
||||
updateUi { uiBuilder.importSeedPhrase.updateError(uiState, null) }
|
||||
}
|
||||
.onFailure {
|
||||
uiState = uiBuilder.importSeedPhrase.updateCreateWalletButton(uiState, enabled = false)
|
||||
val error = it as? SeedPhraseError ?: return
|
||||
|
||||
importedMnemonicComponents = null
|
||||
|
|
@ -199,6 +223,7 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
is SeedPhraseError.InvalidWords -> {
|
||||
uiBuilder.importSeedPhrase.updateInvalidWords(uiState, error.words)
|
||||
}
|
||||
|
||||
else -> uiState
|
||||
}
|
||||
updateUi { uiBuilder.importSeedPhrase.updateError(mediateState, error) }
|
||||
|
|
@ -266,6 +291,7 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
.onSuccess { mnemonic ->
|
||||
generatedMnemonicComponents = mnemonic.mnemonicComponents
|
||||
val mnemonicGridItems = generateMnemonicGridList(mnemonic.mnemonicComponents)
|
||||
setScreenshotsEnabled()
|
||||
updateUi {
|
||||
router.openScreen(SeedPhraseScreen.YourSeedPhrase)
|
||||
uiBuilder.mnemonicGenerated(uiState, mnemonicGridItems.toImmutableList())
|
||||
|
|
@ -278,6 +304,13 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun setScreenshotsEnabled() {
|
||||
router.currentScreen.onEach {
|
||||
val allowScreenshots = it != SeedPhraseScreen.YourSeedPhrase
|
||||
mediator.allowScreenshots(allowScreenshots)
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private suspend fun generateMnemonicGridList(mnemonicComponents: List<String>): ImmutableList<MnemonicGridItem> {
|
||||
val size = mnemonicComponents.size
|
||||
val splitIndex = if (size.isEven()) size / 2 else size / 2 + 1
|
||||
|
|
@ -310,6 +343,7 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
|
||||
private fun buttonContinueClick() {
|
||||
router.openScreen(SeedPhraseScreen.CheckSeedPhrase)
|
||||
mediator.allowScreenshots(true)
|
||||
}
|
||||
|
||||
private fun buttonSuggestedPhraseClick(suggestionIndex: Int) {
|
||||
|
|
@ -368,7 +402,8 @@ class SeedPhraseViewModel @Inject constructor(
|
|||
// endregion Utils
|
||||
|
||||
companion object {
|
||||
private val URI_ABOUT_SEED_PHRASE = Uri.parse("https://ya.ru")
|
||||
private val URI_ABOUT_SEED_PHRASE =
|
||||
Uri.parse("https://tangem.com/ru/blog/post/seed-phrase-a-risky-solution/")
|
||||
private const val MNEMONIC_DEBOUNCER = "MnemonicDebouncer"
|
||||
private const val MNEMONIC_DEBOUNCE_DELAY = 700L
|
||||
private const val DELAY_GENERATE_SEED_PHRASE = 300L
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue