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..4f5f112c24 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, @@ -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), ), ) } 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 b2dec66340..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 @@ -88,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 ce3a707b4d..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,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 } } 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 76a115e8a8..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 @@ -122,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/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index 7ec50bc7ed..bbe596bf2a 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 @@ -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, + ) + } + } + } } } } \ 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/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 77dafd0c4e..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,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 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 5ad4ad1102..1940a2073c 100644 --- a/features/onboarding/build.gradle.kts +++ b/features/onboarding/build.gradle.kts @@ -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) 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 index 2e54d828c6..5775e476a7 100644 --- 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 @@ -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, 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..7089723d17 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,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)) } } \ No newline at end of file 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/viewmodel/SeedPhraseMediator.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseMediator.kt index d0a59581ec..0fc4c110b6 100644 --- 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 @@ -11,4 +11,6 @@ interface SeedPhraseMediator { 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 index 03aef5e7dc..9067f108cd 100644 --- 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 @@ -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 + 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() { 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 10e1a59896..7e4bef9049 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 @@ -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 get() = router.currentScreen - val progress: Int - get() = progressByScreen[currentScreen] ?: 0 + val progress: Flow + 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): ImmutableList { 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