diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 93c54eeefa..bf28d1f618 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -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 { 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) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index c0ffba9893..5c76d3840a 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -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 { + 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) { 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 { + suspend fun resetToFactorySettings(cardId: String? = null): CompletionResult { return runTaskAsyncReturnOnMain( runnable = ResetToFactorySettingsTask(), cardId = cardId, diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index cd729b3b27..d23e9fde5c 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -56,6 +56,7 @@ private data class CreateWalletResponse( class CreateProductWalletTask( private val cardTypesResolver: CardTypesResolver, + private val seed: ByteArray? = null, ) : CardSessionRunnable { 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 { +private class CreateWalletTangemWallet( + private val seed: ByteArray?, +) : ProductCommandProcessor { private var primaryCard: PrimaryCard? = null @@ -149,7 +153,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor + CreateWalletsTask(curves, seed).run(session) { result -> when (result) { is CompletionResult.Success -> { proceedWithCreatedWallets( diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt index 6c41807de9..23f0986e34 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt @@ -18,6 +18,7 @@ class CreateWalletsResponse( class CreateWalletsTask( private val curves: List, + private val seed: ByteArray? = null, ) : CardSessionRunnable { private val createdWalletsResponses = mutableListOf() @@ -37,7 +38,7 @@ class CreateWalletsTask( session: CardSession, callback: (result: CompletionResult) -> 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)) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index cd92b90ea9..b9e5db5876 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -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 } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index 47819aea80..5e6f15834c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -1,13 +1,21 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.net.Uri +import com.tangem.common.CompletionResult +import com.tangem.feature.onboarding.data.model.CreateWalletResponse +import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import org.rekotlin.Action sealed class OnboardingWalletAction : Action { object Init : OnboardingWalletAction() object GetToCreateWalletStep : OnboardingWalletAction() object GetToSaltPayStep : OnboardingWalletAction() + object CreateWallet : OnboardingWalletAction() + data class WalletWasCreated( + val result: CompletionResult, + ) : OnboardingWalletAction() + object Done : OnboardingWalletAction() object FinishOnboarding : OnboardingWalletAction() @@ -19,6 +27,24 @@ sealed class OnboardingWalletAction : Action { object OnBackPressed : OnboardingWalletAction() } +sealed class OnboardingWallet2Action : OnboardingWalletAction() { + data class Init(val maxProgress: Int) : OnboardingWallet2Action() + data class SetDependencies(val maxProgress: Int) : OnboardingWallet2Action() + + data class CreateWallet( + val callback: (CompletionResult) -> Unit, + ) : OnboardingWallet2Action() + + data class ImportWallet( + val mnemonicComponents: List, + val callback: (CompletionResult) -> Unit, + ) : OnboardingWallet2Action() + + data class WalletWasCreated( + val result: CompletionResult, + ) : OnboardingWallet2Action() +} + sealed class BackupAction : Action { object IntroduceBackup : BackupAction() @@ -62,4 +88,6 @@ sealed class BackupAction : Action { object DiscardBackup : BackupAction() object DiscardSavedBackup : BackupAction() object ResumeFoundUnfinishedBackup : BackupAction() + + object ResetBackupCard : BackupAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index a6eafe02d9..f6ac9369d0 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -3,15 +3,17 @@ 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 -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.extensions.withMainContext +import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse +import com.tangem.feature.onboarding.data.model.CreateWalletResponse import com.tangem.operations.backup.BackupService -import com.tangem.tap.backupService +import com.tangem.tap.* import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain @@ -20,6 +22,7 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction @@ -29,12 +32,6 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivat import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import com.tangem.tap.features.wallet.models.toCurrencies import com.tangem.tap.features.wallet.redux.Artwork -import com.tangem.tap.preferencesStorage -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdk -import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userTokensRepository import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.DispatchFunction @@ -47,7 +44,10 @@ object OnboardingWalletMiddleware { private val onboardingWalletMiddleware: Middleware = { dispatch, state -> { next -> { action -> - handleWalletAction(action, state, dispatch) + when (action) { + is OnboardingWallet2Action -> handleWallet2Action(action, state) + else -> handleWalletAction(action, state, dispatch) + } next(action) } } @@ -114,42 +114,44 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: } } is OnboardingWalletAction.CreateWallet -> { + scanResponse ?: return scope.launch { - scanResponse ?: return@launch val result = tangemSdkManager.createProductWallet(scanResponse) - withMainContext { - when (result) { - is CompletionResult.Success -> { - Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) - // here we must use updated scanResponse after createWallet & derivation - val updatedResponse = globalState.onboardingState.onboardingManager.scanResponse.copy( - card = result.data.card, - derivedKeys = result.data.derivedKeys, - primaryCard = result.data.primaryCard, - ) - onboardingManager.scanResponse = updatedResponse - store.state.globalState.topUpController?.registerEmptyWallet(updatedResponse) + store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(result)) + } + } + is OnboardingWalletAction.WalletWasCreated -> { + scanResponse ?: return + when (val result = action.result) { + is CompletionResult.Success -> { + Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) + // here we must use updated scanResponse after createWallet & derivation + val updatedResponse = globalState.onboardingState.onboardingManager.scanResponse.copy( + card = result.data.card, + derivedKeys = result.data.derivedKeys, + primaryCard = result.data.primaryCard, + ) + onboardingManager.scanResponse = updatedResponse + store.state.globalState.topUpController?.registerEmptyWallet(updatedResponse) - val blockchainNetworks = if (DemoHelper.isDemoCardId(result.data.card.cardId)) { - DemoHelper.config.demoBlockchains - } else { - listOf(Blockchain.Bitcoin, Blockchain.Ethereum) - }.map { blockchain -> - BlockchainNetwork(blockchain, result.data.card) - } - - scope.launch { - userTokensRepository.saveUserTokens( - card = result.data.card, - tokens = blockchainNetworks.toCurrencies(), - ) - } - startCardActivation(updatedResponse) - store.dispatch(OnboardingWalletAction.ResumeBackup) - } - is CompletionResult.Failure -> Unit + val blockchainNetworks = if (DemoHelper.isDemoCardId(result.data.card.cardId)) { + DemoHelper.config.demoBlockchains + } else { + listOf(Blockchain.Bitcoin, Blockchain.Ethereum) + }.map { blockchain -> + BlockchainNetwork(blockchain, result.data.card) } + + scope.launch { + userTokensRepository.saveUserTokens( + card = result.data.card, + tokens = blockchainNetworks.toCurrencies(), + ) + } + startCardActivation(updatedResponse) + store.dispatch(OnboardingWalletAction.ResumeBackup) } + is CompletionResult.Failure -> Unit } } OnboardingWalletAction.FinishOnboarding -> { @@ -194,6 +196,93 @@ 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 + + val scanResponse = onboardingManager?.scanResponse + + when (action) { + is OnboardingWallet2Action.Init -> { + if (scanResponse?.cardTypesResolver?.isWallet2() == true) { + store.dispatch(OnboardingWallet2Action.SetDependencies(action.maxProgress)) + } + } + + is OnboardingWallet2Action.CreateWallet -> { + scanResponse ?: return + scope.launch { + val mediateResult = when (val result = tangemSdkManager.createProductWallet(scanResponse)) { + 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.ImportWallet -> { + 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 -> { + val response = CreateProductWalletTaskResponse( + card = mediateResult.data.card, + derivedKeys = mediateResult.data.derivedKeys, + primaryCard = mediateResult.data.primaryCard, + ) + CompletionResult.Success(response) + } + + is CompletionResult.Failure -> { + CompletionResult.Failure(mediateResult.error) + } + } + store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(result)) + } + + else -> Unit + } +} + private fun updateScanResponseAfterBackup(scanResponse: ScanResponse, backupState: BackupState): ScanResponse { val card = if (backupState.backupCardsNumber > 0) { val cardsCount = backupState.backupCardsNumber @@ -263,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, + ), + ) + } + } } } } @@ -368,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 } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index 0c50b1028f..30b94a73c6 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -19,8 +19,8 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet is GlobalAction.Onboarding -> ReducerForGlobalAction.reduce(action, state) is OnboardingSaltPayAction -> OnboardingSaltPayReducer.reduce(action, state) is BackupAction -> state.copy(backupState = BackupReducer.reduce(action, state.backupState, state.isSaltPay)) - - is OnboardingWalletAction.GetToCreateWalletStep -> OnboardingWalletState( + is OnboardingWallet2Action -> OnboardingWallet2Reducer.reduce(action, state) + is OnboardingWalletAction.GetToCreateWalletStep -> state.copy( step = OnboardingWalletStep.CreateWallet, ) is OnboardingWalletAction.GetToSaltPayStep -> state.copy( @@ -65,6 +65,17 @@ private object ReducerForGlobalAction { private fun maxBackupCards(isSaltPay: Boolean): Int = if (isSaltPay) 1 else 2 } +private object OnboardingWallet2Reducer { + fun reduce(action: OnboardingWallet2Action, state: OnboardingWalletState): OnboardingWalletState = when (action) { + is OnboardingWallet2Action.SetDependencies -> state.copy( + wallet2State = OnboardingWallet2State( + maxProgress = action.maxProgress, + ), + ) + else -> state + } +} + private object BackupReducer { @Suppress("ComplexMethod") fun reduce(action: BackupAction, state: BackupState, isSaltPay: Boolean): BackupState { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index d2ef761e70..ce6e2e6dde 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -14,6 +14,7 @@ import org.rekotlin.StateType */ data class OnboardingWalletState( val step: OnboardingWalletStep = OnboardingWalletStep.None, + val wallet2State: OnboardingWallet2State? = null, val backupState: BackupState = BackupState(), val onboardingSaltPayState: OnboardingSaltPayState? = null, val isSaltPay: Boolean = false, @@ -31,14 +32,14 @@ data class OnboardingWalletState( } @Suppress("MagicNumber") - fun getMaxProgress(): Int = when { - isSaltPay -> 12 - else -> 6 + fun getMaxProgress(): Int { + val baseProgress = if (isSaltPay) 12 else 6 + return getWallet2Progress() + baseProgress } @Suppress("ComplexMethod", "MagicNumber") fun getProgressStep(): Int { - return when { + val progressByStep = when { step == OnboardingWalletStep.CreateWallet -> 1 step == OnboardingWalletStep.Backup -> { when (backupState.backupStep) { @@ -69,11 +70,19 @@ data class OnboardingWalletState( step == OnboardingWalletStep.Done -> getMaxProgress() else -> 1 } + + return getWallet2Progress() + progressByStep } + + private fun getWallet2Progress(): Int = wallet2State?.maxProgress ?: 0 } +data class OnboardingWallet2State( + val maxProgress: Int, +) + enum class OnboardingWalletStep { - None, CreateWallet, Backup, SaltPay, Done + None, CreateWallet, SaltPay, Backup, Done } data class BackupState( @@ -113,4 +122,5 @@ sealed class BackupDialog : StateDialog { object BackupInProgress : BackupDialog() object UnfinishedBackupFound : BackupDialog() object ConfirmDiscardingBackup : BackupDialog() + object ResetBackupCard : BackupDialog() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayStateHandler.kt similarity index 95% rename from app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt rename to app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayStateHandler.kt index 394e03adb4..b4c5bf2c60 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayStateHandler.kt @@ -18,15 +18,7 @@ import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.compose.PinCodeWidget -import com.tangem.tap.common.extensions.configureSettings -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.common.extensions.dispatchDialogShow -import com.tangem.tap.common.extensions.getDrawableCompat -import com.tangem.tap.common.extensions.hide -import com.tangem.tap.common.extensions.setDrawable -import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.extensions.stop -import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.feedback.SupportInfo import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen @@ -34,11 +26,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget import com.tangem.tap.common.transitions.InternalNoteLayoutTransition -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.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.redux.* import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebChromeClient import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebViewClient import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog @@ -56,7 +44,7 @@ import com.tangem.wallet.databinding.LayoutOnboardingMainBinding /** [REDACTED_AUTHOR] */ -internal class OnboardingSaltPayView( +internal class OnboardingSaltPayStateHandler( private val walletFragment: OnboardingWalletFragment, ) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingSeedPhraseStateHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingSeedPhraseStateHandler.kt new file mode 100644 index 0000000000..8a26aa71f0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingSeedPhraseStateHandler.kt @@ -0,0 +1,54 @@ +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.OnboardingSeedPhraseApi +import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show +import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState +import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep + +/** +[REDACTED_AUTHOR] + */ +internal class OnboardingSeedPhraseStateHandler( + private val onboardingSeedPhraseApi: OnboardingSeedPhraseApi = OnboardingSeedPhrase(), +) { + + fun newState( + walletFragment: OnboardingWalletFragment, + state: OnboardingWalletState, + seedPhraseViewModel: SeedPhraseViewModel, + ) { + if (state.step == OnboardingWalletStep.CreateWallet && !seedPhraseViewModel.isFinished) { + switchToWallet2SeedPhraseOnboarding(walletFragment, seedPhraseViewModel, state.getMaxProgress()) + } else { + switchToWalletOnboarding(walletFragment, state) + } + } + + private fun switchToWalletOnboarding(walletFragment: OnboardingWalletFragment, state: OnboardingWalletState) { + walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.hide() + walletFragment.pbBinding.pbState.show() + walletFragment.binding.onboardingWalletContainer.show() + walletFragment.handleOnboardingStep(state) + } + + private fun switchToWallet2SeedPhraseOnboarding( + walletFragment: OnboardingWalletFragment, + viewModel: SeedPhraseViewModel, + onboardingWalletMaxProgress: Int, + ) { + walletFragment.pbBinding.pbState.hide() + walletFragment.binding.onboardingWalletContainer.hide() + walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.show() + walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.setContent { + onboardingSeedPhraseApi.ScreenContent( + uiState = viewModel.uiState, + subScreen = viewModel.currentScreen.collectAsState().value, + progress = viewModel.progress.collectAsState(0).value.toFloat() / onboardingWalletMaxProgress, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index 0f8715a237..1c3b28898f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -10,6 +10,7 @@ import android.widget.FrameLayout import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.core.view.MenuProvider +import androidx.fragment.app.viewModels import androidx.transition.TransitionInflater import androidx.transition.TransitionManager import by.kirich1409.viewbindingdelegate.viewBinding @@ -17,46 +18,55 @@ import coil.load import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.tabs.TabLayoutMediator import com.tangem.common.CardIdFormatter +import com.tangem.common.CompletionResult import com.tangem.common.core.CardIdDisplayFormat import com.tangem.core.analytics.Analytics +import com.tangem.feature.onboarding.data.model.CreateWalletResponse +import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseMediator +import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseRouter +import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel 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.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.extensions.* +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.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.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.redux.* import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction -import com.tangem.tap.features.onboarding.products.wallet.saltPay.ui.OnboardingSaltPayView +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 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") +@AndroidEntryPoint class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_wallet), StoreSubscriber, FragmentOnBackPressedHandler { - private val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind) internal val binding: FragmentOnboardingWalletBinding by viewBinding(FragmentOnboardingWalletBinding::bind) + internal val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind) + + internal val bindingSeedPhrase: LayoutOnboardingSeedPhraseBinding by lazy { binding.onboardingSeedPhraseContainer } internal val bindingSaltPay: LayoutOnboardingSaltpayBinding by lazy { binding.onboardingSaltpayContainer } - private val onboardingSaltPayView: OnboardingSaltPayView = OnboardingSaltPayView(this) + private val seedPhraseStateHandler: OnboardingSeedPhraseStateHandler = OnboardingSeedPhraseStateHandler() + private val seedPhraseViewModel by viewModels() + + private val saltPayStateHandler: OnboardingSaltPayStateHandler = OnboardingSaltPayStateHandler(this) private lateinit var cardsWidget: WalletCardsWidget private var accessCodeDialog: AccessCodeDialog? = null @@ -69,6 +79,13 @@ class OnboardingWalletFragment : exitTransition = inflater.inflateTransition(R.transition.fade) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + seedPhraseViewModel.setRouter(makeSeedPhraseRouter()) + seedPhraseViewModel.setMediator(makeSeedPhraseMediator()) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -88,6 +105,7 @@ class OnboardingWalletFragment : binding.toolbar.setNavigationOnClickListener { activity?.onBackPressed() } addBackPressHandler(this) + store.dispatch(OnboardingWallet2Action.Init(seedPhraseViewModel.maxProgress)) store.dispatch(OnboardingWalletAction.Init) store.dispatch(OnboardingSaltPayAction.Init) store.dispatch( @@ -97,7 +115,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 @@ -159,13 +177,19 @@ class OnboardingWalletFragment : pbBinding.pbState.max = state.getMaxProgress() pbBinding.pbState.progress = state.getProgressStep() - if (state.isSaltPay) { - onboardingSaltPayView.newState(state) - } else { - loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard) - loadImageIntoImageView(state.cardArtworkUri, binding.imvFirstBackupCard) - loadImageIntoImageView(state.cardArtworkUri, binding.imvSecondBackupCard) - handleOnboardingStep(state) + when { + state.wallet2State != null -> { + seedPhraseStateHandler.newState(this, state, seedPhraseViewModel) + } + state.isSaltPay -> { + saltPayStateHandler.newState(state) + } + else -> { + loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard) + loadImageIntoImageView(state.cardArtworkUri, binding.imvFirstBackupCard) + loadImageIntoImageView(state.cardArtworkUri, binding.imvSecondBackupCard) + handleOnboardingStep(state) + } } } @@ -180,7 +204,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 -> {} } } @@ -201,9 +230,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() @@ -215,7 +244,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() @@ -230,7 +259,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) } @@ -271,7 +300,7 @@ class OnboardingWalletFragment : layoutButtonsAddCards.btnContinue.isEnabled = state.backupCardsNumber != 0 if (isSaltPay) { - onboardingSaltPayView.showAddBackupCards(state) + saltPayStateHandler.showAddBackupCards(state) } else { when (state.backupCardsNumber) { 0 -> { @@ -443,4 +472,46 @@ class OnboardingWalletFragment : resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true) return typedValue.float } + + private fun makeSeedPhraseRouter(): SeedPhraseRouter = SeedPhraseRouter( + onBack = ::handleOnBackPressed, + onOpenChat = { + store.dispatch(GlobalAction.OpenChat(SupportInfo())) + }, + onOpenUriClick = { uri -> + store.dispatchOpenUrl(uri.toString()) + }, + ) + + private fun makeSeedPhraseMediator(): SeedPhraseMediator { + return object : SeedPhraseMediator { + override fun createWallet(callback: (CompletionResult) -> Unit) { + store.dispatch(OnboardingWallet2Action.CreateWallet(callback)) + } + + override fun onWalletCreated(result: CompletionResult) { + store.dispatch(OnboardingWallet2Action.WalletWasCreated(result)) + } + + override fun importWallet( + mnemonicComponents: List, + callback: (CompletionResult) -> Unit, + ) { + 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, + ) + } + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ResetBackupCardDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ResetBackupCardDialog.kt new file mode 100644 index 0000000000..352972273e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ResetBackupCardDialog.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_wallet.xml b/app/src/main/res/layout/fragment_onboarding_wallet.xml index 33174db903..59a23e9ab4 100644 --- a/app/src/main/res/layout/fragment_onboarding_wallet.xml +++ b/app/src/main/res/layout/fragment_onboarding_wallet.xml @@ -228,6 +228,13 @@ android:layout_height="match_parent" android:visibility="gone" /> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_onboarding_progress.xml b/app/src/main/res/layout/view_onboarding_progress.xml index 1c7c3b53fc..1d840e7958 100644 --- a/app/src/main/res/layout/view_onboarding_progress.xml +++ b/app/src/main/res/layout/view_onboarding_progress.xml @@ -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" /> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 9544c3520b..ce094f06b8 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -45,12 +45,11 @@ #007AFF - + #000000 #FFFFFF #F5F5F5 - #EBEBEB #1E1E1E #EBEBEB diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index f95eb69b21..af94ad33ae 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -184,6 +184,7 @@ В этом случае вам будет необходимо начать процесс заново. Вы хотите выйти из процесса активации? Подготовка + Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Хотите сбросить его и использовать карту для бэкапа? Подтвердите свою личность Верификация клиента Код доступа diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index ba65e1aa64..038f2a7fe9 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -182,6 +182,7 @@ In this case, you will need to start from the beginning. Do you want to exit the activation process? Getting started + 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? Verify your identity KYC Pin code diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt b/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt index d62e391dfb..9f0b627cb5 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt @@ -26,7 +26,9 @@ class TangemCardTypesResolver( card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable && !card.isSaltPay - override fun isWallet2(): Boolean = card.firmwareVersion >= FirmwareVersion.KeysImportAvailable + 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 diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt index fa9bb0ee84..a8c2c7e12b 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt @@ -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, 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, ) } diff --git a/features/onboarding/build.gradle.kts b/features/onboarding/build.gradle.kts index 61df104ad9..1940a2073c 100644 --- a/features/onboarding/build.gradle.kts +++ b/features/onboarding/build.gradle.kts @@ -10,6 +10,7 @@ plugins { dependencies { /** Core modules */ implementation(project(":common")) + implementation(project(":domain:models")) implementation(project(":core:featuretoggles")) implementation(project(":core:datasource")) implementation(project(":core:analytics")) @@ -17,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) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/api/OnboardingSeedPhrase.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/api/OnboardingSeedPhrase.kt new file mode 100644 index 0000000000..2db7541c5a --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/api/OnboardingSeedPhrase.kt @@ -0,0 +1,79 @@ +package com.tangem.feature.onboarding.api + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.* +import androidx.compose.material.LinearProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState +import com.tangem.feature.onboarding.presentation.wallet2.ui.* +import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseScreen + +/** +[REDACTED_AUTHOR] + */ +class OnboardingSeedPhrase : OnboardingSeedPhraseApi { + + @Composable + override fun ScreenContent(uiState: OnboardingSeedPhraseState, subScreen: SeedPhraseScreen, progress: Float) { + BackHandler(onBack = uiState.onBackClick) + TangemTheme { + Column { + ProgressIndicator(progress) + Content(subScreen, uiState) + } + } + } +} + +@Composable +private fun ProgressIndicator(progress: Float) { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(top = TangemTheme.dimens.size8), + ) { + LinearProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.size16), + color = TangemTheme.colors.background.action, + backgroundColor = TangemTheme.colors.background.action.copy(alpha = 0.081f), + progress = progress, + ) + } +} + +@Composable +private fun Content(screen: SeedPhraseScreen, uiState: OnboardingSeedPhraseState) { + when (screen) { + SeedPhraseScreen.Intro -> { + IntroScreen( + state = uiState.introState, + ) + } + + SeedPhraseScreen.AboutSeedPhrase -> { + AboutSeedPhraseScreen( + state = uiState.aboutState, + ) + } + SeedPhraseScreen.YourSeedPhrase -> { + YourSeedPhraseScreen( + state = uiState.yourSeedPhraseState, + ) + } + SeedPhraseScreen.CheckSeedPhrase -> { + CheckSeedPhraseScreen( + state = uiState.checkSeedPhraseState, + ) + } + SeedPhraseScreen.ImportSeedPhrase -> { + ImportSeedPhraseScreen( + state = uiState.importSeedPhraseState, + ) + } + } +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/api/OnboardingSeedPhraseApi.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/api/OnboardingSeedPhraseApi.kt new file mode 100644 index 0000000000..43ac6afa71 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/api/OnboardingSeedPhraseApi.kt @@ -0,0 +1,14 @@ +package com.tangem.feature.onboarding.api + +import androidx.compose.runtime.Composable +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState +import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseScreen + +/** +[REDACTED_AUTHOR] + */ +interface OnboardingSeedPhraseApi { + @Suppress("TopLevelComposableFunctions") + @Composable + fun ScreenContent(uiState: OnboardingSeedPhraseState, subScreen: SeedPhraseScreen, progress: Float) +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt index 116b7239c3..9cb238b99e 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt @@ -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 by lazy { bip39Wordlist.words.toHashSet() } override fun getWordsDictionary(): Set = 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" - } } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/di/OnboardingDataModule.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/di/OnboardingDataModule.kt index fe7e292403..a853bf6c77 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/di/OnboardingDataModule.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/di/OnboardingDataModule.kt @@ -1,11 +1,14 @@ package com.tangem.feature.onboarding.data.di -import com.tangem.datasource.asset.AssetReader +import android.content.Context +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 +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @@ -15,7 +18,7 @@ class OnboardingDataModule { @Provides @Singleton - fun provideSeedPhraseSdkRepository(assetReader: AssetReader): MnemonicRepository { - return DefaultMnemonicRepository(assetReader) + fun provideSeedPhraseSdkRepository(@ApplicationContext context: Context): MnemonicRepository { + return DefaultMnemonicRepository(Wordlist.getWordlist(context)) } } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/model/CreateWalletResponse.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/model/CreateWalletResponse.kt new file mode 100644 index 0000000000..76f4027a1b --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/model/CreateWalletResponse.kt @@ -0,0 +1,15 @@ +package com.tangem.feature.onboarding.data.model + +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.domain.models.scan.CardDTO +import com.tangem.operations.backup.PrimaryCard +import com.tangem.operations.derivation.ExtendedPublicKeysMap + +/** +[REDACTED_AUTHOR] + */ +data class CreateWalletResponse( + val card: CardDTO, + val derivedKeys: Map = mapOf(), + val primaryCard: PrimaryCard? = null, +) \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt index 9eea61c93b..74b732e86b 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt @@ -9,20 +9,15 @@ import kotlinx.collections.immutable.persistentListOf [REDACTED_AUTHOR] */ data class OnboardingSeedPhraseState( - val step: OnboardingSeedPhraseStep, val introState: IntroState, val aboutState: AboutState, val yourSeedPhraseState: YourSeedPhraseState, val checkSeedPhraseState: CheckSeedPhraseState, val importSeedPhraseState: ImportSeedPhraseState, val menuButtonChat: ButtonState, - val isOnboardingFinished: Boolean = false, + val onBackClick: () -> Unit, ) -enum class OnboardingSeedPhraseStep { - Intro, AboutSeedPhrase, YourSeedPhrase, CheckSeedPhrase, ImportSeedPhrase -} - data class IntroState( val cardImageUrl: String? = null, val buttonCreateWallet: ButtonState, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt index 419b277b9d..5a63039118 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt @@ -12,6 +12,7 @@ data class UiActions( val checkSeedPhraseActions: CheckSeedPhraseUiAction, val importSeedPhraseActions: ImportSeedPhraseUiAction, val menuChatClick: () -> Unit, + val menuNavigateBackClick: () -> Unit, ) data class IntroUiAction( diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index 1db145ce7f..805aee6175 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -93,7 +93,6 @@ private fun PhraseBlock(state: ImportSeedPhraseState, modifier: Modifier = Modif ), colors = TangemTextFieldsDefault.defaultTextFieldColors, ) - Box( modifier = Modifier diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt index f240c101ce..2b58078ed0 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt @@ -14,7 +14,6 @@ class StateBuilder( val importSeedPhrase: ImportSeedPhraseStateBuilder = ImportSeedPhraseStateBuilder() fun init(): OnboardingSeedPhraseState = OnboardingSeedPhraseState( - step = OnboardingSeedPhraseStep.Intro, introState = IntroState( buttonCreateWallet = ButtonState( onClick = uiActions.introActions.buttonCreateWalletClick, @@ -77,13 +76,9 @@ class StateBuilder( menuButtonChat = ButtonState( onClick = uiActions.menuChatClick, ), + onBackClick = uiActions.menuNavigateBackClick, ) - fun changeStep(uiState: OnboardingSeedPhraseState, step: OnboardingSeedPhraseStep): OnboardingSeedPhraseState = - uiState.copy( - step = step, - ) - fun generateMnemonicComponents(uiState: OnboardingSeedPhraseState): OnboardingSeedPhraseState { return uiState.copy( aboutState = uiState.aboutState.copy( @@ -103,7 +98,6 @@ class StateBuilder( ): OnboardingSeedPhraseState { return updateMnemonicComponents(uiState, mnemonicGridItems) .copy( - step = OnboardingSeedPhraseStep.YourSeedPhrase, aboutState = uiState.aboutState.copy( buttonGenerateSeedPhrase = uiState.aboutState.buttonGenerateSeedPhrase.copy( showProgress = false, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseMediator.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseMediator.kt new file mode 100644 index 0000000000..0fc4c110b6 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseMediator.kt @@ -0,0 +1,16 @@ +package com.tangem.feature.onboarding.presentation.wallet2.viewmodel + +import com.tangem.common.CompletionResult +import com.tangem.feature.onboarding.data.model.CreateWalletResponse + +/** +[REDACTED_AUTHOR] + */ +interface SeedPhraseMediator { + fun createWallet(callback: (CompletionResult) -> Unit) + fun importWallet(mnemonicComponents: List, callback: (CompletionResult) -> Unit) + + fun onWalletCreated(result: CompletionResult) + + fun allowScreenshots(allow: Boolean) +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseRouter.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseRouter.kt new file mode 100644 index 0000000000..9067f108cd --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseRouter.kt @@ -0,0 +1,56 @@ +package com.tangem.feature.onboarding.presentation.wallet2.viewmodel + +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] + */ +class SeedPhraseRouter( + private val onBack: () -> Unit, + private val onOpenChat: () -> Unit, + private val onOpenUriClick: (Uri) -> Unit, +) { + + private val _currentScreen = MutableStateFlow(SeedPhraseScreen.Intro) + + val currentScreen: StateFlow + get() = _currentScreen + + var currentScreenState by mutableStateOf(SeedPhraseScreen.Intro) + private set + + fun navigateBack() { + _currentScreen.value = when (_currentScreen.value) { + SeedPhraseScreen.Intro -> { + onBack.invoke() + return + } + + SeedPhraseScreen.AboutSeedPhrase -> SeedPhraseScreen.Intro + SeedPhraseScreen.YourSeedPhrase -> SeedPhraseScreen.AboutSeedPhrase + SeedPhraseScreen.CheckSeedPhrase -> SeedPhraseScreen.YourSeedPhrase + SeedPhraseScreen.ImportSeedPhrase -> SeedPhraseScreen.AboutSeedPhrase + } + } + + fun openScreen(screen: SeedPhraseScreen) { + _currentScreen.value = screen + } + + fun openChat() { + onOpenChat.invoke() + } + + fun openUri(uri: Uri) { + onOpenUriClick.invoke(uri) + } +} + +enum class SeedPhraseScreen { + Intro, AboutSeedPhrase, YourSeedPhrase, CheckSeedPhrase, ImportSeedPhrase +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index 4fbee7352f..ad9c1c4dae 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -1,14 +1,27 @@ package com.tangem.feature.onboarding.presentation.wallet2.viewmodel +import android.net.Uri import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +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 @@ -16,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 @@ -34,18 +56,48 @@ class SeedPhraseViewModel @Inject constructor( var uiState: OnboardingSeedPhraseState by mutableStateOf(uiBuilder.init()) private set - val currentStep: OnboardingSeedPhraseStep - get() = uiState.step + private lateinit var router: SeedPhraseRouter + private lateinit var mediator: SeedPhraseMediator + + val currentScreen: StateFlow + get() = router.currentScreen + + val progress: Flow + get() = currentScreen.map { progressByScreen[it] ?: 0 } + + val maxProgress: Int by lazy { progressByScreen.maxOfOrNull { it.value } ?: 0 } + + var isFinished: Boolean = false + private set + + private val progressByScreen = mapOf( + SeedPhraseScreen.Intro to 1, + SeedPhraseScreen.AboutSeedPhrase to 2, + SeedPhraseScreen.YourSeedPhrase to 3, + SeedPhraseScreen.CheckSeedPhrase to 3, + SeedPhraseScreen.ImportSeedPhrase to 3, + ) private val textFieldsDebouncers = mutableMapOf() private var suggestionWordInserted: AtomicBoolean = AtomicBoolean(false) + private var generatedMnemonicComponents: List? = null + private var importedMnemonicComponents: List? = null + override fun onCleared() { textFieldsDebouncers.forEach { entry -> entry.value.release() } textFieldsDebouncers.clear() super.onCleared() } + fun setRouter(router: SeedPhraseRouter) { + this.router = router + } + + fun setMediator(mediator: SeedPhraseMediator) { + this.mediator = mediator + } + private fun createUiActions(): UiActions = UiActions( introActions = IntroUiAction( buttonCreateWalletClick = ::buttonCreateWalletClick, @@ -60,7 +112,7 @@ class SeedPhraseViewModel @Inject constructor( buttonContinueClick = ::buttonContinueClick, ), checkSeedPhraseActions = CheckSeedPhraseUiAction( - buttonCreateWalletClick = ::buttonCreateWalletWithSeedPhraseClick, + buttonCreateWalletClick = { buttonImportWalletClick(generatedMnemonicComponents) }, secondTextFieldAction = TextFieldUiAction( onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Second, value) }, ), @@ -76,9 +128,10 @@ class SeedPhraseViewModel @Inject constructor( onTextFieldChanged = { value -> onSeedPhraseTextFieldChanged(value) }, ), suggestedPhraseClick = ::buttonSuggestedPhraseClick, - buttonCreateWalletClick = ::buttonCreateWalletWithSeedPhraseClick, + buttonCreateWalletClick = { buttonImportWalletClick(importedMnemonicComponents) }, ), menuChatClick = ::menuChatClick, + menuNavigateBackClick = ::menuNavigateBackClick, ) // region CheckSeedPhrase @@ -116,8 +169,7 @@ 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) val fieldState = uiState.importSeedPhraseState.tvSeedPhrase val inputMnemonic = fieldState.textFieldValue.text @@ -148,21 +200,29 @@ class SeedPhraseViewModel @Inject constructor( private suspend fun validateMnemonic(inputMnemonic: String) { if (inputMnemonic.isEmpty() && uiState.importSeedPhraseState.error != null) { - updateUi { uiBuilder.importSeedPhrase.updateError(uiState, null) } + val mediateState = uiBuilder.importSeedPhrase.updateCreateWalletButton(uiState, enabled = false) + updateUi { + uiBuilder.importSeedPhrase.updateError(mediateState, null) + } return } interactor.validateMnemonicString(inputMnemonic) - .onSuccess { - updateUi { uiBuilder.importSeedPhrase.updateError(uiState, null) } + .onSuccess { mnemonicComponents -> + importedMnemonicComponents = mnemonicComponents + val mediateState = uiBuilder.importSeedPhrase.updateCreateWalletButton(uiState, enabled = true) + updateUi { uiBuilder.importSeedPhrase.updateError(mediateState, null) } } .onFailure { + uiState = uiBuilder.importSeedPhrase.updateCreateWalletButton(uiState, enabled = false) val error = it as? SeedPhraseError ?: return + importedMnemonicComponents = null val mediateState = when (error) { is SeedPhraseError.InvalidWords -> { uiBuilder.importSeedPhrase.updateInvalidWords(uiState, error.words) } + else -> uiState } updateUi { uiBuilder.importSeedPhrase.updateError(mediateState, error) } @@ -180,22 +240,46 @@ class SeedPhraseViewModel @Inject constructor( // endregion ImportSeedPhrase // region ButtonClickHandlers - @Suppress("EmptyFunctionBlock") private fun buttonCreateWalletClick() { - } - - @Suppress("EmptyFunctionBlock") - private fun buttonCreateWalletWithSeedPhraseClick() { - } - - private fun buttonOtherOptionsClick() { - viewModelScope.launchSingle { - updateUi { uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.AboutSeedPhrase) } + viewModelScope.launch(dispatchers.io) { + mediator.createWallet(::handleWalletCreationResult) } } + private fun buttonImportWalletClick(mnemonicComponents: List?) { + mnemonicComponents ?: return + + viewModelScope.launch(dispatchers.io) { + mediator.importWallet(mnemonicComponents, ::handleWalletCreationResult) + } + } + + private fun handleWalletCreationResult(result: CompletionResult) { + when (result) { + is CompletionResult.Success -> { + isFinished = true + } + is CompletionResult.Failure -> { + // errors shows on the TangemSdk bottom sheet dialog + } + } + mediator.onWalletCreated(result) + } + + private fun menuNavigateBackClick() { + router.navigateBack() + } + + private fun menuChatClick() { + router.openChat() + } + + private fun buttonOtherOptionsClick() { + router.openScreen(SeedPhraseScreen.AboutSeedPhrase) + } + private fun buttonReadMoreAboutSeedPhraseClick() { - // TODO: open the about info through a router + router.openUri(URI_ABOUT_SEED_PHRASE) } private fun buttonGenerateSeedPhraseClick() { @@ -204,15 +288,28 @@ class SeedPhraseViewModel @Inject constructor( delay(DELAY_GENERATE_SEED_PHRASE) interactor.generateMnemonic() .onSuccess { mnemonic -> + generatedMnemonicComponents = mnemonic.mnemonicComponents val mnemonicGridItems = generateMnemonicGridList(mnemonic.mnemonicComponents) - updateUi { uiBuilder.mnemonicGenerated(uiState, mnemonicGridItems.toImmutableList()) } + setScreenshotsEnabled() + updateUi { + router.openScreen(SeedPhraseScreen.YourSeedPhrase) + uiBuilder.mnemonicGenerated(uiState, mnemonicGridItems.toImmutableList()) + } } .onFailure { + generatedMnemonicComponents = null // TODO: show error } } } + private fun setScreenshotsEnabled() { + router.currentScreen.onEach { + val allowScreenshots = it != SeedPhraseScreen.YourSeedPhrase + mediator.allowScreenshots(allowScreenshots) + }.launchIn(viewModelScope) + } + private suspend fun generateMnemonicGridList(mnemonicComponents: List): ImmutableList { val size = mnemonicComponents.size val splitIndex = if (size.isEven()) size / 2 else size / 2 + 1 @@ -240,15 +337,12 @@ class SeedPhraseViewModel @Inject constructor( } private fun buttonImportSeedPhraseClick() { - viewModelScope.launchSingle { - updateUi { uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.ImportSeedPhrase) } - } + router.openScreen(SeedPhraseScreen.ImportSeedPhrase) } private fun buttonContinueClick() { - viewModelScope.launchSingle { - updateUi { uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.CheckSeedPhrase) } - } + router.openScreen(SeedPhraseScreen.CheckSeedPhrase) + mediator.allowScreenshots(true) } private fun buttonSuggestedPhraseClick(suggestionIndex: Int) { @@ -269,10 +363,6 @@ class SeedPhraseViewModel @Inject constructor( } } } - - private fun menuChatClick() { - // TODO: open the support chat through a router - } // endregion ButtonClickHandlers // region Utils @@ -311,6 +401,8 @@ class SeedPhraseViewModel @Inject constructor( // endregion Utils companion object { + 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