Updated on 2026-08-14
This commit is contained in:
parent
9b6de19ebe
commit
ce602107bb
16 changed files with 178 additions and 115 deletions
|
|
@ -55,6 +55,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
)
|
||||
is OnboardingDialog.TwinningProcessNotCompleted -> TwinningProcessNotCompletedDialog.create(context)
|
||||
is OnboardingDialog.InterruptOnboarding -> InterruptOnboardingDialog.create(context, state.dialog)
|
||||
is OnboardingDialog.WalletActivationError -> WalletActivationErrorDialog.create(context, state.dialog)
|
||||
is WalletConnectDialog.UnsupportedCard ->
|
||||
SimpleAlertDialog.create(
|
||||
titleRes = R.string.wallet_connect_title,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ sealed class GlobalAction : Action {
|
|||
data class StartForUnfinishedBackup(val addedBackupCardsCount: Int) : Onboarding()
|
||||
|
||||
object Stop : Onboarding()
|
||||
|
||||
data class ShouldResetCardOnCreate(val shouldReset: Boolean) : Onboarding()
|
||||
}
|
||||
|
||||
object ScanFailsCounter {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,11 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
|
|||
is GlobalAction.Onboarding.Stop -> {
|
||||
globalState.copy(onboardingState = OnboardingState(false))
|
||||
}
|
||||
is GlobalAction.Onboarding.ShouldResetCardOnCreate -> {
|
||||
globalState.copy(
|
||||
onboardingState = globalState.onboardingState.copy(shouldResetOnCreate = action.shouldReset),
|
||||
)
|
||||
}
|
||||
is GlobalAction.ScanFailsCounter.Increment -> {
|
||||
globalState.copy(scanCardFailsCounter = globalState.scanCardFailsCounter + 1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,4 +36,5 @@ typealias CryptoCurrencyName = String
|
|||
data class OnboardingState(
|
||||
val onboardingStarted: Boolean = false,
|
||||
val onboardingManager: OnboardingManager? = null,
|
||||
val shouldResetOnCreate: Boolean = false,
|
||||
)
|
||||
|
|
@ -85,11 +85,15 @@ class TangemSdkManager(
|
|||
).also { sendScanResultsToAnalytics(it) }
|
||||
}
|
||||
|
||||
suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult<CreateProductWalletTaskResponse> {
|
||||
suspend fun createProductWallet(
|
||||
scanResponse: ScanResponse,
|
||||
shouldReset: Boolean = false,
|
||||
): CompletionResult<CreateProductWalletTaskResponse> {
|
||||
return runTaskAsync(
|
||||
runnable = CreateProductWalletTask(
|
||||
cardTypesResolver = scanResponse.cardTypesResolver,
|
||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
shouldReset = shouldReset,
|
||||
),
|
||||
cardId = scanResponse.card.cardId,
|
||||
initialMessage = Message(resources.getString(R.string.initial_message_create_wallet_body)),
|
||||
|
|
@ -100,6 +104,7 @@ class TangemSdkManager(
|
|||
suspend fun importWallet(
|
||||
scanResponse: ScanResponse,
|
||||
mnemonic: String,
|
||||
shouldReset: Boolean,
|
||||
): CompletionResult<CreateProductWalletTaskResponse> {
|
||||
val defaultMnemonic = try {
|
||||
DefaultMnemonic(mnemonic, tangemSdk.wordlist)
|
||||
|
|
@ -108,9 +113,10 @@ class TangemSdkManager(
|
|||
}
|
||||
return runTaskAsync(
|
||||
CreateProductWalletTask(
|
||||
scanResponse.cardTypesResolver,
|
||||
cardTypesResolver = scanResponse.cardTypesResolver,
|
||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
defaultMnemonic,
|
||||
mnemonic = defaultMnemonic,
|
||||
shouldReset = shouldReset,
|
||||
),
|
||||
scanResponse.card.cardId,
|
||||
Message(resources.getString(R.string.initial_message_create_wallet_body)),
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
package com.tangem.tap.domain.tasks
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.operations.PreflightReadMode
|
||||
import com.tangem.operations.PreflightReadTask
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
|
||||
@Deprecated("Use CreateProductWalletAndRescanTask instead")
|
||||
class CreateWalletAndRescanTask : CardSessionRunnable<Card> {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
|
||||
val card = session.environment.card.guard {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CardError()))
|
||||
return
|
||||
}
|
||||
val firmwareVersion = card.firmwareVersion
|
||||
|
||||
val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
|
||||
CreateWalletTask(card.supportedCurves.first())
|
||||
} else {
|
||||
CreateWalletsTask()
|
||||
}
|
||||
|
||||
task.run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success ->
|
||||
PreflightReadTask(PreflightReadMode.FullCardRead).run(session, callback)
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
package com.tangem.tap.domain.tasks
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.operations.PreflightReadMode
|
||||
import com.tangem.operations.PreflightReadTask
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
|
||||
@Deprecated("Use CreateProductWalletTask instead")
|
||||
class CreateWalletsTask(curves: List<EllipticCurve>? = null) : CardSessionRunnable<Card> {
|
||||
|
||||
private val curves = curves ?: listOf(
|
||||
EllipticCurve.Secp256k1,
|
||||
EllipticCurve.Ed25519,
|
||||
EllipticCurve.Secp256r1,
|
||||
)
|
||||
|
||||
private var index = 0
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
|
||||
val curve = curves[index]
|
||||
createWallet(curve, session, callback)
|
||||
}
|
||||
|
||||
private fun createWallet(
|
||||
curve: EllipticCurve,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<Card>) -> Unit,
|
||||
) {
|
||||
CreateWalletTask(curve).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
if (index == curves.lastIndex) {
|
||||
PreflightReadTask(PreflightReadMode.FullCardRead).run(session, callback)
|
||||
return@run
|
||||
}
|
||||
index += 1
|
||||
createWallet(curves[index], session, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.tap.domain.tasks.product
|
||||
|
||||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
|
||||
class CardInitializationValidator(private val expectedCurves: List<EllipticCurve>) {
|
||||
|
||||
fun validateWallets(wallets: List<CardWallet>): Boolean {
|
||||
val curves = wallets.map { it.curve }.toSet()
|
||||
return curves.size == expectedCurves.size &&
|
||||
curves.containsAll(expectedCurves)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
|
|
@ -24,6 +25,7 @@ import com.tangem.operations.backup.PrimaryCard
|
|||
import com.tangem.operations.backup.StartPrimaryCardLinkingCommand
|
||||
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.operations.read.ReadWalletsListCommand
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.operations.wallet.CreateWalletResponse as SdkCreateWalletResponse
|
||||
|
|
@ -60,6 +62,7 @@ class CreateProductWalletTask(
|
|||
private val cardTypesResolver: CardTypesResolver,
|
||||
private val derivationStyleProvider: DerivationStyleProvider,
|
||||
private val mnemonic: Mnemonic? = null,
|
||||
private val shouldReset: Boolean,
|
||||
) : CardSessionRunnable<CreateProductWalletTaskResponse> {
|
||||
|
||||
override val allowsRequestAccessCodeFromRepository: Boolean = false
|
||||
|
|
@ -79,7 +82,7 @@ class CreateProductWalletTask(
|
|||
cardTypesResolver.isTangemTwins() ->
|
||||
throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet")
|
||||
|
||||
else -> CreateWalletTangemWallet(mnemonic, derivationStyleProvider)
|
||||
else -> CreateWalletTangemWallet(mnemonic, shouldReset, derivationStyleProvider, cardDto)
|
||||
}
|
||||
commandProcessor.proceed(cardDto, session) {
|
||||
when (it) {
|
||||
|
|
@ -139,38 +142,38 @@ private class CreateWalletTangemNote(private val cardTypesResolver: CardTypesRes
|
|||
*/
|
||||
private class CreateWalletTangemWallet(
|
||||
private val mnemonic: Mnemonic?,
|
||||
private val shouldReset: Boolean,
|
||||
private val derivationStyleProvider: DerivationStyleProvider,
|
||||
cardDTO: CardDTO,
|
||||
) : ProductCommandProcessor<CreateProductWalletTaskResponse> {
|
||||
|
||||
private var primaryCard: PrimaryCard? = null
|
||||
private val cardConfig = CardConfig.createConfig(cardDTO)
|
||||
|
||||
override fun proceed(
|
||||
card: CardDTO,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
|
||||
) {
|
||||
val config = CardConfig.createConfig(card)
|
||||
val walletsOnCard = card.wallets.map { it.curve }.toSet()
|
||||
val curves = config.mandatoryCurves.toSet()
|
||||
.intersect(card.supportedCurves.toSet())
|
||||
.subtract(walletsOnCard).toList()
|
||||
|
||||
if (curves.isEmpty()) {
|
||||
val createWalletResponses = card.wallets.map { wallet ->
|
||||
CreateWalletResponse(card.cardId, wallet)
|
||||
}
|
||||
proceedWithCreatedWallets(card, createWalletResponses, session, callback)
|
||||
return
|
||||
if (walletsOnCard.isEmpty()) {
|
||||
createMultiWallet(card, session, callback)
|
||||
} else if (shouldReset) {
|
||||
resetCard(card, session, callback)
|
||||
} else {
|
||||
callback(CompletionResult.Failure(TangemSdkError.WalletAlreadyCreated()))
|
||||
}
|
||||
CreateWalletsTask(curves, mnemonic).run(session) { result ->
|
||||
}
|
||||
|
||||
private fun createMultiWallet(
|
||||
card: CardDTO,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
|
||||
) {
|
||||
CreateWalletsTask(cardConfig.mandatoryCurves, mnemonic).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
proceedWithCreatedWallets(
|
||||
card = card,
|
||||
createWalletResponses = result.data.createWalletResponses.map { CreateWalletResponse(it) },
|
||||
session = session,
|
||||
callback = callback,
|
||||
)
|
||||
checkIfAllWalletsCreated(card, session, result.data, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
|
|
@ -179,6 +182,61 @@ private class CreateWalletTangemWallet(
|
|||
}
|
||||
}
|
||||
|
||||
private fun checkIfAllWalletsCreated(
|
||||
card: CardDTO,
|
||||
session: CardSession,
|
||||
createResponse: CreateWalletsResponse,
|
||||
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
|
||||
) {
|
||||
if (card.firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
|
||||
proceedWithCreatedWallets(
|
||||
card = card,
|
||||
createWalletResponses = createResponse.createWalletResponses.map { CreateWalletResponse(it) },
|
||||
session = session,
|
||||
callback = callback,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val command = ReadWalletsListCommand()
|
||||
command.run(session) { response ->
|
||||
when (response) {
|
||||
is CompletionResult.Success -> {
|
||||
val cardInitializationValidator = CardInitializationValidator(cardConfig.mandatoryCurves)
|
||||
if (cardInitializationValidator.validateWallets(response.data.wallets)) {
|
||||
proceedWithCreatedWallets(
|
||||
card = card,
|
||||
createWalletResponses = createResponse.createWalletResponses.map {
|
||||
CreateWalletResponse(it)
|
||||
},
|
||||
session = session,
|
||||
callback = callback,
|
||||
)
|
||||
} else {
|
||||
callback(CompletionResult.Failure(TangemSdkError.WalletAlreadyCreated()))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetCard(
|
||||
card: CardDTO,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
|
||||
) {
|
||||
val resetCommand = ResetToFactorySettingsTask(allowsRequestAccessCodeFromRepository = false)
|
||||
resetCommand.run(session) {
|
||||
when (it) {
|
||||
is CompletionResult.Success -> {
|
||||
createMultiWallet(card, session, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithCreatedWallets(
|
||||
card: CardDTO,
|
||||
createWalletResponses: List<CreateWalletResponse>,
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ import com.tangem.core.navigation.StateDialog
|
|||
sealed class OnboardingDialog : StateDialog {
|
||||
object TwinningProcessNotCompleted : OnboardingDialog()
|
||||
data class InterruptOnboarding(val onOk: VoidCallback) : OnboardingDialog()
|
||||
data class WalletActivationError(val onConfirm: () -> Unit) : OnboardingDialog()
|
||||
}
|
||||
|
|
@ -120,7 +120,10 @@ private fun handleWalletAction(action: Action) {
|
|||
is OnboardingWalletAction.CreateWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.createProductWallet(scanResponse)
|
||||
val result = tangemSdkManager.createProductWallet(
|
||||
scanResponse,
|
||||
globalState.onboardingState.shouldResetOnCreate,
|
||||
)
|
||||
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(true, result))
|
||||
}
|
||||
}
|
||||
|
|
@ -139,10 +142,15 @@ private fun handleWalletAction(action: Action) {
|
|||
)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(false))
|
||||
startCardActivation(updatedResponse)
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.WalletAlreadyCreated) {
|
||||
handleActivationError()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingWalletAction.FinishOnboarding -> {
|
||||
|
|
@ -218,9 +226,15 @@ private fun handleWallet2Action(action: OnboardingWallet2Action) {
|
|||
is OnboardingWallet2Action.CreateWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
val mediateResult = when (val result = tangemSdkManager.createProductWallet(scanResponse)) {
|
||||
val mediateResult = when (
|
||||
val result = tangemSdkManager.createProductWallet(
|
||||
scanResponse,
|
||||
globalState.onboardingState.shouldResetOnCreate,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(false))
|
||||
val response = CreateWalletResponse(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
|
|
@ -230,6 +244,9 @@ private fun handleWallet2Action(action: OnboardingWallet2Action) {
|
|||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.WalletAlreadyCreated) {
|
||||
handleActivationError()
|
||||
}
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
|
|
@ -246,6 +263,7 @@ private fun handleWallet2Action(action: OnboardingWallet2Action) {
|
|||
val result = tangemSdkManager.importWallet(
|
||||
scanResponse = scanResponse,
|
||||
mnemonic = action.mnemonicComponents.joinToString(" "),
|
||||
shouldReset = globalState.onboardingState.shouldResetOnCreate,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
|
|
@ -259,6 +277,7 @@ private fun handleWallet2Action(action: OnboardingWallet2Action) {
|
|||
seedPhraseLength = action.mnemonicComponents.size,
|
||||
),
|
||||
)
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(false))
|
||||
val response = CreateWalletResponse(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
|
|
@ -268,6 +287,9 @@ private fun handleWallet2Action(action: OnboardingWallet2Action) {
|
|||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.WalletAlreadyCreated) {
|
||||
handleActivationError()
|
||||
}
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
|
|
@ -300,6 +322,16 @@ private fun handleWallet2Action(action: OnboardingWallet2Action) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleActivationError() {
|
||||
store.dispatchDialogShow(
|
||||
OnboardingDialog.WalletActivationError(
|
||||
onConfirm = {
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(true))
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateScanResponseAfterBackup(scanResponse: ScanResponse, backupState: BackupState): ScanResponse {
|
||||
val card = if (backupState.backupCardsNumber > 0) {
|
||||
val cardsCount = backupState.backupCardsNumber
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import com.tangem.feature.onboarding.api.OnboardingSeedPhrase
|
||||
import com.tangem.feature.onboarding.api.OnboardingSeedPhraseScreen
|
||||
import com.tangem.feature.onboarding.api.OnboardingSeedPhraseApi
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseScreen
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel
|
||||
|
|
@ -15,7 +15,7 @@ import com.tangem.wallet.R
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class OnboardingSeedPhraseStateHandler(
|
||||
private val onboardingSeedPhraseApi: OnboardingSeedPhraseApi = OnboardingSeedPhrase(),
|
||||
private val onboardingSeedPhraseApi: OnboardingSeedPhraseApi = OnboardingSeedPhraseScreen(),
|
||||
) {
|
||||
|
||||
fun newState(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.feedback.SupportInfo
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object WalletActivationErrorDialog {
|
||||
|
||||
fun create(context: Context, dialog: OnboardingDialog.WalletActivationError): Dialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(context.getString(R.string.onboarding_activation_error_title))
|
||||
setMessage(context.getString(R.string.onboarding_activation_error_message))
|
||||
setPositiveButton(R.string.common_ok) { _, _ -> dialog.onConfirm() }
|
||||
setNegativeButton(R.string.chat_button_title) { _, _ ->
|
||||
store.dispatch(GlobalAction.OpenChat(SupportInfo()))
|
||||
}
|
||||
setOnDismissListener { store.dispatchDialogHide() }
|
||||
setCancelable(false)
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue