diff --git a/app/build.gradle b/app/build.gradle index 8a098dc5a8..c8e2711c22 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,9 +77,9 @@ dependencies { implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation 'com.tangem:blockchain:develop-48' - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-99' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-99' + implementation 'com.tangem:blockchain:develop-51' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-106' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-106' // WebView implementation "androidx.browser:browser:1.3.0" diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 43eaa90ca3..0c9f4a0bbf 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -96,8 +96,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0 val isScannedBefore = store.state.globalState.scanResponse != null - val isOnboardingServiceActive = store.state.globalState.onboardingManager != null - if (backStackIsEmpty && (!isOnboardingServiceActive && !isScannedBefore)) { + val isOnboardingServiceActive = store.state.globalState.onboardingState.onboardingStarted + if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore)) { store.dispatch(NavigationAction.NavigateTo(AppScreen.Home)) } intentHandler.handleIntent(intent) 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 6533bab1a3..8f1491368c 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -10,8 +10,8 @@ import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.ui.dialog.CreateWalletInterruptDialog import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog +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.BuyMoreBackupCardsDialog import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog @@ -79,7 +79,7 @@ class DialogManager : StoreSubscriber { TransactionDialog.create(state.dialog.dialogData, context) is WalletConnectDialog.PersonalSign -> PersonalSignDialog.create(state.dialog.data, context) - is BackupDialog.BuyMoreBackupCards -> BuyMoreBackupCardsDialog.create(context) + is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context) is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context) is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context) is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt index 5ef395e4ad..233fa3702d 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt @@ -103,6 +103,8 @@ object TangemSdk { is TangemSdkError.FilesDisabled -> TangemSdkError.FilesDisabled() is TangemSdkError.HDWalletDisabled -> TangemSdkError.HDWalletDisabled() is TangemSdkError.WrongInteractionMode -> TangemSdkError.WrongInteractionMode() + is TangemSdkError.IssuerSignatureLoadingFailed -> TangemSdkError.IssuerSignatureLoadingFailed() + is TangemSdkError.BackupFailedFirmware -> TangemSdkError.BackupFailedFirmware() } } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/ByteArray.kt b/app/src/main/java/com/tangem/tap/common/extensions/ByteArray.kt new file mode 100644 index 0000000000..376b56f399 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/ByteArray.kt @@ -0,0 +1,17 @@ +package com.tangem.tap.common.extensions + +/** +[REDACTED_AUTHOR] + */ + +data class ByteArrayKey(val bytes: ByteArray) { + override fun equals(other: Any?): Boolean { + return this === other || other is ByteArrayKey && this.bytes.contentEquals(other.bytes) + } + + override fun hashCode(): Int = bytes.contentHashCode() +} + +fun ByteArray.toMapKey(): ByteArrayKey = ByteArrayKey(this) + +fun ByteArrayKey.bytes(): ByteArray = this.bytes \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 36723da434..30d8e1b18a 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -68,7 +68,6 @@ fun FragmentActivity.addOnBackPressedDispatcher( private fun fragmentFactory(screen: AppScreen): Fragment { return when (screen) { -// AppScreen.Home -> TestLeapfrogFragment() AppScreen.Home -> HomeFragment() AppScreen.OnboardingNote -> OnboardingNoteFragment() AppScreen.OnboardingWallet -> OnboardingWalletFragment() diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index af62d98fff..bbea6e43f9 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -31,11 +31,12 @@ sealed class GlobalAction : Action { object HideDialog : GlobalAction() sealed class Onboarding { - data class Start(val scanResponse: ScanResponse, val fromHomeScreen: Boolean = true) : GlobalAction() + data class Start(val scanResponse: ScanResponse?, val fromHomeScreen: Boolean = true) : GlobalAction() object Stop : GlobalAction() } data class ScanCard( + val shouldDeriveWC: Boolean, val onSuccess: ((ScanResponse) -> Unit)? = null, val onFailure: ((TangemError) -> Unit)? = null, val messageResId: Int? = null, diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index 06cc349269..258131199b 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -3,6 +3,7 @@ package com.tangem.tap.common.redux.global import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result +import com.tangem.tap.* import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain @@ -13,10 +14,6 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.moonpay.MoonpayService -import com.tangem.tap.preferencesStorage -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager import kotlinx.coroutines.launch import org.rekotlin.Middleware import timber.log.Timber @@ -91,7 +88,12 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState } is GlobalAction.ScanCard -> { scope.launch { - val result = tangemSdkManager.scanProduct(FirebaseAnalyticsHandler, action.messageResId) + val result = tangemSdkManager.scanProduct( + FirebaseAnalyticsHandler, + currenciesRepository, + action.shouldDeriveWC, + action.messageResId + ) store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) withMainContext { when (result) { diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 993830329a..e549a2f386 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -17,12 +17,16 @@ fun globalReducer(action: Action, state: AppState): GlobalState { globalState.copy(resources = action.resources) } is GlobalAction.Onboarding.Start -> { - val usedCardsPrefStorage = preferencesStorage.usedCardsPrefStorage - val onboardingState = OnboardingManager(action.scanResponse, usedCardsPrefStorage) - globalState.copy(onboardingManager = onboardingState) + val onboardingManager = if (action.scanResponse != null) { + val usedCardsPrefStorage = preferencesStorage.usedCardsPrefStorage + OnboardingManager(action.scanResponse, usedCardsPrefStorage) + } else { + null + } + globalState.copy(onboardingState = OnboardingState(true, onboardingManager)) } is GlobalAction.Onboarding.Stop -> { - globalState.copy(onboardingManager = null) + globalState.copy(onboardingState = OnboardingState(false)) } is GlobalAction.ScanFailsCounter.Increment -> { globalState.copy(scanCardFailsCounter = globalState.scanCardFailsCounter + 1) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index 484142c611..f634a6a0ff 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -15,7 +15,7 @@ import org.rekotlin.StateType data class GlobalState( val scanResponse: ScanResponse? = null, - val onboardingManager: OnboardingManager? = null, + val onboardingState: OnboardingState = OnboardingState(), val cardVerifiedOnline: Boolean = false, val tapWalletManager: TapWalletManager = TapWalletManager(), val payIdManager: PayIdManager = PayIdManager(), @@ -32,12 +32,17 @@ data class GlobalState( data class AndroidResources( - val strings: RString = RString() + val strings: RString = RString(), ) { data class RString( val addressWasCopied: Int = -1, - val walletIsNotEmpty: Int = -1 + val walletIsNotEmpty: Int = -1, ) } typealias CryptoCurrencyName = String -typealias FiatCurrencyName = String \ No newline at end of file +typealias FiatCurrencyName = String + +data class OnboardingState( + val onboardingStarted: Boolean = false, + val onboardingManager: OnboardingManager? = null, +) \ No newline at end of file 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 695f3b83f0..7131feb7b7 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -1,6 +1,6 @@ package com.tangem.tap.domain -import CreateProductWalletAndRescanTask +import CreateProductWalletTask import android.content.Context import com.tangem.Message import com.tangem.TangemSdk @@ -12,7 +12,10 @@ import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.Config import com.tangem.common.core.TangemSdkError +import com.tangem.common.hdWallet.DerivationPath import com.tangem.operations.CommandResponse +import com.tangem.operations.derivation.DeriveWalletPublicKeysTask +import com.tangem.operations.derivation.ExtendedPublicKeyList import com.tangem.operations.pins.CheckUserCodesCommand import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.operations.pins.SetUserCodeCommand @@ -23,6 +26,7 @@ import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.tasks.product.ScanResponse +import com.tangem.tap.domain.tokens.CurrenciesRepository import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -33,18 +37,20 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun scanProduct( analyticsHandler: AnalyticsHandler, + currenciesRepository: CurrenciesRepository, + shouldDeriveWC: Boolean, messageRes: Int? = null, ): CompletionResult { analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null) val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) - return runTaskAsyncReturnOnMain(ScanProductTask(), null, message) - .also { sendScanFailuresToAnalytics(analyticsHandler, it) } + return runTaskAsyncReturnOnMain(ScanProductTask(null, currenciesRepository, shouldDeriveWC), null, message) + .also { sendScanFailuresToAnalytics(analyticsHandler, it) } } suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult { return runTaskAsync( - CreateProductWalletAndRescanTask(scanResponse.productType), + CreateProductWalletTask(scanResponse.productType), scanResponse.card.cardId, Message(context.getString(R.string.initial_message_create_wallet_body)) ) @@ -66,6 +72,14 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body))) } + suspend fun derivePublicKeys( + cardId: String, + walletPublicKey: ByteArray, + derivationPaths: List + ): CompletionResult { + return runTaskAsyncReturnOnMain(DeriveWalletPublicKeysTask(walletPublicKey, derivationPaths), cardId) + } + suspend fun resetToFactorySettings(card: Card): CompletionResult { return runTaskAsyncReturnOnMain( ResetToFactorySettingsTask(), @@ -104,13 +118,13 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun runTaskAsync( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, ): CompletionResult = - withContext(Dispatchers.Main) { - suspendCoroutine { continuation -> - tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result -> - continuation.resume(result) - } + withContext(Dispatchers.Main) { + suspendCoroutine { continuation -> + tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result -> + continuation.resume(result) } } + } private suspend fun runTaskAsyncReturnOnMain( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt index 978ca5288f..20d1eda08c 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt @@ -3,6 +3,7 @@ package com.tangem.tap.domain import com.tangem.Message import com.tangem.TangemSdk import com.tangem.blockchain.common.TransactionSigner +import com.tangem.blockchain.common.Wallet import com.tangem.common.CompletionResult import com.tangem.tap.domain.tasks.SignHashTask import com.tangem.tap.domain.tasks.SignHashesTask @@ -15,13 +16,9 @@ class TangemSigner( private val signerCallback: (TangemSignerResponse) -> Unit, ) : TransactionSigner { - override suspend fun sign( - hash: ByteArray, - cardId: String, - walletPublicKey: ByteArray, - ): CompletionResult = - suspendCoroutine { continuation -> - val command = SignHashTask(hash, walletPublicKey) + override suspend fun sign(hash: ByteArray, cardId: String, publicKey: Wallet.PublicKey): CompletionResult { + return suspendCoroutine { continuation -> + val command = SignHashTask(hash, publicKey) tangemSdk.startSessionWithRunnable( runnable = command, cardId = cardId, @@ -42,15 +39,11 @@ class TangemSigner( } } } + } - - override suspend fun sign( - hashes: List, - cardId: String, - walletPublicKey: ByteArray, - ): CompletionResult> = - suspendCoroutine { continuation -> - val task = SignHashesTask(hashes, walletPublicKey) + override suspend fun sign(hashes: List, cardId: String, publicKey: Wallet.PublicKey): CompletionResult> { + return suspendCoroutine { continuation -> + val task = SignHashesTask(hashes, publicKey) tangemSdk.startSessionWithRunnable( runnable = task, cardId = cardId, @@ -71,6 +64,7 @@ class TangemSigner( } } } + } } data class TangemSignerResponse( diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 10f1a76a87..9f6d0f13e1 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -1,7 +1,6 @@ package com.tangem.tap.domain import com.tangem.blockchain.common.* -import com.tangem.common.card.Card import com.tangem.common.services.Result import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler @@ -16,7 +15,6 @@ import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.extensions.* import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.domain.tokens.CardCurrencies -import com.tangem.tap.features.tokens.redux.TokensAction import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity @@ -85,12 +83,6 @@ class TapWalletManager { store.dispatch(GlobalAction.SaveScanNoteResponse(data)) store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard)) store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed)) - - val blockchain = data.getBlockchain() - if (blockchain == Blockchain.Ethereum || - blockchain == Blockchain.EthereumTestnet) { - store.dispatch(TokensAction.LoadCardTokens) - } loadData(data) } } @@ -131,7 +123,7 @@ class TapWalletManager { store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken)) } if (data.card.isMultiwalletAllowed) { - loadMultiWalletData(data.card, blockchain, primaryWalletManager) + loadMultiWalletData(data, blockchain, primaryWalletManager) } else { store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(blockchain))) @@ -139,7 +131,7 @@ class TapWalletManager { } else { if (data.card.isMultiwalletAllowed) { - loadMultiWalletData(data.card, blockchain, null) + loadMultiWalletData(data, blockchain, null) } } val moonPayStatus = store.state.globalState.moonpayStatus @@ -149,10 +141,10 @@ class TapWalletManager { } private fun loadMultiWalletData( - card: Card, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager? + scanResponse: ScanResponse, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager? ) { val primaryTokens = primaryWalletManager?.cardTokens?.toList() ?: emptyList() - val savedCurrencies = currenciesRepository.loadCardCurrencies(card.cardId) + val savedCurrencies = currenciesRepository.loadCardCurrencies(scanResponse.card.cardId) if (savedCurrencies == null) { if (primaryBlockchain != null && primaryWalletManager != null) { @@ -169,11 +161,11 @@ class TapWalletManager { CardCurrencies(blockchains = blockchains, tokens = emptyList()) )) val walletManagers = - walletManagerFactory.makeWalletManagersForApp(card, blockchains.toList()) + walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains.toList()) store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers)) store.dispatch(WalletAction.MultiWallet.AddBlockchains(blockchains.toList())) } - store.dispatch(WalletAction.MultiWallet.FindBlockchainsInUse(card, walletManagerFactory)) + store.dispatch(WalletAction.MultiWallet.FindBlockchainsInUse) store.dispatch(WalletAction.MultiWallet.FindTokensInUse) } else { val blockchains = savedCurrencies.blockchains.toList() @@ -182,10 +174,10 @@ class TapWalletManager { primaryWalletManager != null && primaryBlockchain != null ) { val blockchainsWithoutPrimary = blockchains.filterNot { it == primaryBlockchain } - walletManagerFactory.makeWalletManagersForApp(card, blockchainsWithoutPrimary) + walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchainsWithoutPrimary) .plus(primaryWalletManager) } else { - walletManagerFactory.makeWalletManagersForApp(card, blockchains) + walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains) } store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers)) diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt index aa85331ab2..b505619739 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -3,74 +3,70 @@ package com.tangem.tap.domain.extensions import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.common.card.Card import com.tangem.common.card.CardWallet import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.hexToBytes +import com.tangem.tap.common.extensions.toMapKey import com.tangem.tap.domain.TapWorkarounds.isTestCard import com.tangem.tap.domain.tasks.product.ScanResponse fun WalletManagerFactory.makeWalletManagerForApp( - card: Card, - blockchain: Blockchain, + scanResponse: ScanResponse, blockchain: Blockchain ): WalletManager? { + val card = scanResponse.card + if (card.isTestCard && blockchain.getTestnetVersion() == null) return null val supportedCurves = blockchain.getSupportedCurves() ?: return null + val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) } - val wallet = selectWallet(wallets) - val publicKey = wallet?.publicKey ?: return null - val curveToUse = wallet.curve ?: return null - return if (card.isTestCard) { - blockchain.getTestnetVersion()?.let { - makeWalletManager(card.cardId, publicKey, it, curveToUse) + val wallet = selectWallet(wallets) ?: return null + + val environmentBlockchain = if (card.isTestCard) blockchain.getTestnetVersion()!! else blockchain + + val seedKey = wallet.extendedPublicKey + return when { + scanResponse.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> { + makeTwinWalletManager( + card.cardId, + wallet.publicKey, scanResponse.secondTwinPublicKey.hexToBytes(), + environmentBlockchain, wallet.curve + ) + } + seedKey != null -> { + val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()] + val derivedKey = derivedKeys?.firstOrNull { it.derivationPath == blockchain.derivationPath() } + ?: return null + + makeWalletManager(card.cardId, environmentBlockchain, seedKey, derivedKey) + } + else -> { + makeWalletManager(card.cardId, environmentBlockchain, wallet.publicKey, wallet.curve) } - } else { - makeWalletManager(card.cardId, publicKey, blockchain, curveToUse) } } +fun WalletManagerFactory.makeWalletManagersForApp( + scanResponse: ScanResponse, blockchains: List, +): List { + val isTestCard = scanResponse.card.isTestCard + val filteredBlockchains = blockchains.mapNotNull { if (isTestCard) it.getTestnetVersion() else it } + return filteredBlockchains.mapNotNull { makeWalletManagerForApp(scanResponse, it) } +} + +fun WalletManagerFactory.makePrimaryWalletManager( + scanResponse: ScanResponse, +): WalletManager? { + val blockchain = if (scanResponse.card.isTestCard) { + scanResponse.getBlockchain().getTestnetVersion() ?: return null + } else { + scanResponse.getBlockchain() + } + return makeWalletManagerForApp(scanResponse, blockchain) +} + private fun selectWallet(wallets: List): CardWallet? { return when (wallets.size) { 0 -> null 1 -> wallets[0] else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0] } -} - -fun WalletManagerFactory.makeWalletManagersForApp( - card: Card, blockchains: List, -): List { - return if (card.isTestCard) { - blockchains.mapNotNull { blockchain -> - blockchain.getTestnetVersion()?.let { makeWalletManagerForApp(card, it) } - } - } else { - blockchains.mapNotNull { blockchain -> makeWalletManagerForApp(card, blockchain) } - } -} - -fun WalletManagerFactory.makePrimaryWalletManager( - data: ScanResponse, -): WalletManager? { - val card = data.card - val blockchain = if (card.isTestCard) { - data.getBlockchain().getTestnetVersion() - } else { - data.getBlockchain() - } - val supportedCurves = blockchain?.getSupportedCurves() ?: return null - - val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) } - val wallet = selectWallet(wallets) - val publicKey = wallet?.publicKey ?: return null - val curveToUse = wallet.curve - - return if (data.isTangemTwins() && data.secondTwinPublicKey != null) { - makeMultisigWalletManager( - cardId = card.cardId, - walletPublicKey = publicKey, pairPublicKey = data.secondTwinPublicKey.hexToBytes(), - blockchain = blockchain, curve = curveToUse - ) - } else { - makeWalletManager(card.cardId, publicKey, blockchain, curveToUse) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt index 1487f07e37..81a967c20b 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt @@ -9,7 +9,7 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask -import com.tangem.operations.wallet.CreateWalletCommand +import com.tangem.operations.wallet.CreateWalletTask @Deprecated("Use CreateProductWalletAndRescanTask instead") class CreateWalletAndRescanTask : CardSessionRunnable { @@ -22,7 +22,7 @@ class CreateWalletAndRescanTask : CardSessionRunnable { val firmwareVersion = card.firmwareVersion val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) { - CreateWalletCommand(card.supportedCurves.first()) + CreateWalletTask(card.supportedCurves.first()) } else { CreateWalletsTask() } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt index 6de34f651a..140a0ef72d 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt @@ -7,9 +7,9 @@ import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask -import com.tangem.operations.wallet.CreateWalletCommand +import com.tangem.operations.wallet.CreateWalletTask -@Deprecated("Use CreateProductWalletsResponse instead") +@Deprecated("Use CreateProductWalletTask instead") class CreateWalletsTask(curves: List? = null) : CardSessionRunnable { private val curves = curves ?: listOf( @@ -30,7 +30,7 @@ class CreateWalletsTask(curves: List? = null) : CardSessionRunnab callback: (result: CompletionResult) -> Unit ) { - CreateWalletCommand(curve).run(session) { result -> + CreateWalletTask(curve).run(session) { result -> when (result) { is CompletionResult.Success -> { if (index == curves.lastIndex) { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt index 3c24418b42..58520125ce 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt @@ -1,5 +1,6 @@ package com.tangem.tap.domain.tasks +import com.tangem.blockchain.common.Wallet import com.tangem.common.CompletionResult import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable @@ -15,17 +16,17 @@ class TangemSignHashResponse( class SignHashTask( private val hash: ByteArray, - private val walletPublicKey: ByteArray, + private val publicKey: Wallet.PublicKey, ) : CardSessionRunnable { override fun run(session: CardSession, callback: CompletionCallback) { - SignHashCommand(hash, walletPublicKey).run(session) { response -> + SignHashCommand(hash, publicKey.seedKey, publicKey.derivationPath).run(session) { response -> when (response) { is CompletionResult.Success -> { callback(CompletionResult.Success(TangemSignHashResponse( response.data.signature, response.data.totalSignedHashes, - session.environment.card?.wallet(walletPublicKey)?.remainingSignatures + session.environment.card?.wallet(publicKey.seedKey)?.remainingSignatures ))) } is CompletionResult.Failure -> diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/SignHashesTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashesTask.kt index 2755d9aa82..4d25a1de56 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/SignHashesTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashesTask.kt @@ -1,5 +1,6 @@ package com.tangem.tap.domain.tasks +import com.tangem.blockchain.common.Wallet import com.tangem.common.CompletionResult import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable @@ -15,16 +16,16 @@ class TangemSignHashesResponse( class SignHashesTask( private val hashes: Collection, - private val walletPublicKey: ByteArray, + private val publicKey: Wallet.PublicKey, ) : CardSessionRunnable { override fun run(session: CardSession, callback: CompletionCallback) { - SignHashesCommand(hashes.toTypedArray(), walletPublicKey).run(session) { response -> + SignHashesCommand(hashes.toTypedArray(), publicKey.seedKey, publicKey.derivationPath).run(session) { response -> when (response) { is CompletionResult.Success -> { callback(CompletionResult.Success(TangemSignHashesResponse( response.data.signatures, response.data.totalSignedHashes, - session.environment.card?.wallet(walletPublicKey)?.remainingSignatures + session.environment.card?.wallet(publicKey.seedKey)?.remainingSignatures ))) } is CompletionResult.Failure -> diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt similarity index 64% rename from app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt rename to app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index 12ce0fdb66..530b53ac4c 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -1,25 +1,29 @@ +import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult import com.tangem.common.card.Card +import com.tangem.common.card.CardWallet import com.tangem.common.card.EllipticCurve import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard -import com.tangem.operations.PreflightReadMode -import com.tangem.operations.PreflightReadTask -import com.tangem.operations.wallet.CreateWalletCommand +import com.tangem.operations.derivation.DeriveWalletPublicKeysTask +import com.tangem.operations.derivation.ExtendedPublicKeyList import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.operations.wallet.CreateWalletTask +import com.tangem.tap.common.extensions.toMapKey import com.tangem.tap.domain.ProductType import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain -import com.tangem.tap.domain.tasks.product.CreateProductWalletsTask +import com.tangem.tap.domain.tasks.product.CreateWalletsTask import com.tangem.tap.domain.tasks.product.ProductCommandProcessor import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets +import com.tangem.tap.store /** [REDACTED_AUTHOR] */ -class CreateProductWalletAndRescanTask( +class CreateProductWalletTask( private val type: ProductType, ) : CardSessionRunnable { @@ -28,33 +32,19 @@ class CreateProductWalletAndRescanTask( callback(CompletionResult.Failure(TangemSdkError.CardError())) return } - CreateProductWalletTask(card, type).run(session) { result -> - when (result) { - is CompletionResult.Success -> PreflightReadTask(PreflightReadMode.FullCardRead).run( - session, - callback) - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } -} -private class CreateProductWalletTask( - private val card: Card, - private val type: ProductType, -) : CardSessionRunnable { - - override fun run( - session: CardSession, - callback: (result: CompletionResult) -> Unit, - ) { val commandProcessor = when (type) { ProductType.Note -> CreateWalletTangemNote() ProductType.Twins -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet") ProductType.Wallet -> CreateWalletTangemWallet() else -> CreateWalletOtherCards() } - commandProcessor.proceed(card, session, callback) + commandProcessor.proceed(card, session) { + when (it) { + is CompletionResult.Success -> callback(CompletionResult.Success(session.environment.card!!)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error)) + } + } } } @@ -84,13 +74,13 @@ private class CreateWalletTangemNote : ProductCommandProcessor { + override fun proceed( card: Card, session: CardSession, @@ -98,16 +88,37 @@ private class CreateWalletTangemWallet : ProductCommandProcessor + CreateWalletsTask(curves).run(session) { result -> when (result) { - is CompletionResult.Success -> - callback(CompletionResult.Success(result.data.createWalletResponses[0])) + is CompletionResult.Success -> { + val response = result.data.createWalletResponses[0] + val derivationPaths = listOf(Blockchain.Bitcoin, Blockchain.Ethereum) + .mapNotNull { it.derivationPath() } + + DeriveWalletPublicKeysTask(response.wallet.publicKey, derivationPaths).run(session) { + when (it) { + is CompletionResult.Success -> { + updateDerivedKeys(response.wallet, it.data) + callback(CompletionResult.Success(response)) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error)) + } + } + } is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } } +//TODO: updating derived wallet public keys - make it better later +private fun updateDerivedKeys(wallet: CardWallet, derivedKeys: ExtendedPublicKeyList) { + val onboardingManager = store.state.globalState.onboardingState.onboardingManager ?: return + onboardingManager.scanResponse = onboardingManager.scanResponse.copy( + derivedKeys = mapOf(wallet.publicKey.toMapKey() to derivedKeys) + ) +} + private class CreateWalletOtherCards : ProductCommandProcessor { override fun proceed( @@ -117,9 +128,9 @@ private class CreateWalletOtherCards : ProductCommandProcessor diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt similarity index 76% rename from app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletsTask.kt rename to app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt index fc00138a57..89130c4a16 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt @@ -5,23 +5,23 @@ import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.operations.CommandResponse -import com.tangem.operations.wallet.CreateWalletCommand import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.operations.wallet.CreateWalletTask /** [REDACTED_AUTHOR] */ -class CreateProductWalletsResponse( +class CreateWalletsResponse( val createWalletResponses: List ) : CommandResponse -class CreateProductWalletsTask( +class CreateWalletsTask( private val curves: List, -) : CardSessionRunnable { +) : CardSessionRunnable { private val createdWalletsResponses = mutableListOf() - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { val curve = curves[createdWalletsResponses.size] createWallet(curve, session, callback) } @@ -29,15 +29,14 @@ class CreateProductWalletsTask( private fun createWallet( curve: EllipticCurve, session: CardSession, - callback: (result: CompletionResult) -> Unit + callback: (result: CompletionResult) -> Unit ) { - - CreateWalletCommand(curve).run(session) { result -> + CreateWalletTask(curve).run(session) { result -> when (result) { is CompletionResult.Success -> { createdWalletsResponses.add(result.data) if (createdWalletsResponses.size == curves.size) { - callback(CompletionResult.Success(CreateProductWalletsResponse(createdWalletsResponses))) + callback(CompletionResult.Success(CreateWalletsResponse(createdWalletsResponses))) return@run } createWallet(curves[createdWalletsResponses.size], session, callback) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt index f475ef12ef..444a7b3971 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt @@ -40,7 +40,7 @@ class ResetToFactorySettingsTask() : CardSessionRunnable { ) { val backupStatus = session.environment.card?.backupStatus - if (backupStatus == null && backupStatus == Card.BackupStatus.NoBackup) { + if (backupStatus == null || backupStatus == Card.BackupStatus.NoBackup) { callback(CompletionResult.Success(session.environment.card!!)) return } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 14f454c6b5..4aa99403d2 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -14,11 +14,16 @@ import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard import com.tangem.common.extensions.toHexString +import com.tangem.common.hdWallet.DerivationPath +import com.tangem.common.hdWallet.ExtendedPublicKey import com.tangem.operations.CommandResponse import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask import com.tangem.operations.ScanTask +import com.tangem.operations.derivation.DeriveWalletPublicKeysTask import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand +import com.tangem.tap.common.extensions.ByteArrayKey +import com.tangem.tap.common.extensions.toMapKey import com.tangem.tap.domain.ProductType import com.tangem.tap.domain.TapSdkError import com.tangem.tap.domain.TapWorkarounds @@ -26,6 +31,7 @@ import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain import com.tangem.tap.domain.TapWorkarounds.isExcluded import com.tangem.tap.domain.TapWorkarounds.isNotSupportedInThatRelease import com.tangem.tap.domain.extensions.getSingleWallet +import com.tangem.tap.domain.tokens.CurrenciesRepository import com.tangem.tap.domain.twins.TwinsHelper data class ScanResponse( @@ -33,6 +39,7 @@ data class ScanResponse( val productType: ProductType, val walletData: WalletData?, val secondTwinPublicKey: String? = null, + val derivedKeys: Map> = mapOf() ) : CommandResponse { fun getBlockchain(): Blockchain { @@ -60,7 +67,13 @@ data class ScanResponse( fun twinsIsTwinned(): Boolean = card.isTangemTwins() && walletData != null && secondTwinPublicKey != null } -class ScanProductTask(val card: Card? = null) : CardSessionRunnable { +typealias KeyWalletPublicKey = ByteArrayKey + +class ScanProductTask( + val card: Card? = null, + private val currenciesRepository: CurrenciesRepository?, + private val shouldDeriveWC: Boolean +) : CardSessionRunnable { override fun run( session: CardSession, @@ -80,7 +93,7 @@ class ScanProductTask(val card: Card? = null) : CardSessionRunnable ScanNoteProcessor() card.isTangemTwins() -> ScanTwinProcessor() - TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor() + TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor(currenciesRepository, shouldDeriveWC) else -> ScanOtherCardsProcessor() } commandProcessor.proceed(card, session) { processorResult -> @@ -116,13 +129,60 @@ private class ScanNoteProcessor : ProductCommandProcessor { } } -private class ScanWalletProcessor : ProductCommandProcessor { +private class ScanWalletProcessor( + private val currenciesRepository: CurrenciesRepository?, + private val shouldDeriveWC: Boolean +) : ProductCommandProcessor { + override fun proceed( card: Card, session: CardSession, callback: (result: CompletionResult) -> Unit ) { - callback(CompletionResult.Success(ScanResponse(card, ProductType.Wallet, session.environment.walletData))) + val derivationPaths = collectDerivationPaths(card)?.distinct() + val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } + + if (derivationPaths.isNullOrEmpty() || wallet == null || wallet.chainCode == null) { + callback(CompletionResult.Success(ScanResponse(card, ProductType.Wallet, session.environment.walletData))) + return + } + + DeriveWalletPublicKeysTask(wallet.publicKey, derivationPaths).run(session) { result -> + when (result) { + is CompletionResult.Success -> { + val derivedKeys = mapOf(wallet.publicKey.toMapKey() to result.data) + val response = ScanResponse( + card, + ProductType.Wallet, + session.environment.walletData, + derivedKeys = derivedKeys + ) + callback(CompletionResult.Success(response)) + + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } + + private fun collectDerivationPaths(card: Card): List? { + val currenciesRepository = currenciesRepository ?: return null + val cardCurrencies = currenciesRepository.loadCardCurrencies(card.cardId) + + val blockchainsToDerive = if (cardCurrencies == null) { + mutableListOf(Blockchain.Bitcoin, Blockchain.Ethereum) + } else { + val tokenBlockchains = cardCurrencies.tokens.map { it.blockchain } + (cardCurrencies.blockchains + tokenBlockchains).toMutableList() + } + + if (shouldDeriveWC) { + blockchainsToDerive.addAll(listOf(Blockchain.Ethereum, Blockchain.Binance, Blockchain.EthereumTestnet)) + } + + return blockchainsToDerive.toSet() + .filter { it.getSupportedCurves()?.contains(EllipticCurve.Secp256k1) == true } + .mapNotNull { it.derivationPath() } } } @@ -174,7 +234,7 @@ private class ScanOtherCardsProcessor : ProductCommandProcessor { return } - CreateProductWalletsTask(curvesToCreate).run(session) { result -> + CreateWalletsTask(curvesToCreate).run(session) { result -> when (result) { is CompletionResult.Success -> { PreflightReadTask(PreflightReadMode.FullCardRead, card.cardId).run(session) { readResult -> diff --git a/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt b/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt index 775d3748a7..9dd6e91960 100644 --- a/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt @@ -35,7 +35,7 @@ class TopUpManager { store.dispatch( GlobalAction.UpdateWalletSignedHashes( walletSignedHashes = signResponse.totalSignedHashes, - walletPublicKey = walletManager.wallet.publicKey, + walletPublicKey = walletManager.wallet.publicKey.seedKey, remainingSignatures = signResponse.remainingSignatures ) ) diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt index 400ee19ae3..872c130c30 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -4,8 +4,8 @@ import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable -import com.tangem.operations.wallet.CreateWalletCommand import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand import com.tangem.tap.domain.extensions.getSingleWallet @@ -20,15 +20,13 @@ class CreateFirstTwinWalletTask : CardSessionRunnable { when (response) { is CompletionResult.Success -> { session.environment.card = session.environment.card?.setWallets(emptyList()) - CreateWalletCommand(EllipticCurve.Secp256k1) - .run(session) { callback(it) } + CreateWalletTask(EllipticCurve.Secp256k1).run(session) { callback(it) } } is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error)) } } } else { - CreateWalletCommand(EllipticCurve.Secp256k1) - .run(session) { callback(it) } + CreateWalletTask(EllipticCurve.Secp256k1).run(session) { callback(it) } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index 24b908cb0a..f7ef809d38 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -8,8 +8,8 @@ import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.TangemError import com.tangem.common.extensions.hexToBytes -import com.tangem.operations.wallet.CreateWalletCommand import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand import com.tangem.tap.domain.extensions.getSingleWallet import com.tangem.wallet.R @@ -47,7 +47,7 @@ class CreateSecondTwinWalletTask( private fun finishTask(session: CardSession, callback: (result: CompletionResult) -> Unit) { session.setMessage(creatingWalletMessage) - CreateWalletCommand(EllipticCurve.Secp256k1).run(session) { result -> + CreateWalletTask(EllipticCurve.Secp256k1).run(session) { result -> when (result) { is CompletionResult.Success -> { session.environment.card = session.environment.card?.updateWallet(result.data.wallet) diff --git a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt index c035973ac7..6fb4bb28c6 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt @@ -23,7 +23,7 @@ class FinalizeTwinTask( PreflightReadTask(PreflightReadMode.FullCardRead).run(session) { readResult -> when (readResult) { is CompletionResult.Success -> - ScanProductTask(readResult.data).run(session, callback) + ScanProductTask(readResult.data, null, false).run(session, callback) is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error)) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index f93217e447..7c74264429 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -2,7 +2,6 @@ package com.tangem.tap.domain.walletconnect import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.guard -import com.tangem.common.extensions.hexToBytes import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.global.GlobalAction @@ -97,10 +96,10 @@ class WalletConnectManager { val activeData = sessions[session] ?: return removeSimilarSessions(activeData) + val key = activeData.wallet.derivedPublicKey ?: activeData.wallet.walletPublicKey + val accounts = listOf(Blockchain.Ethereum.makeAddresses(key).first().value) val approved = activeData.client.approveSession( - accounts = listOf(Blockchain.Ethereum.makeAddresses( - activeData.wallet.walletPublicKey.hexToBytes()).first().value - ), + accounts = accounts, chainId = activeData.wallet.chainId ) if (approved) { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index e2ac447999..85ef0eaac6 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -5,14 +5,12 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumUtils import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.Companion.toKeccak -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.TransactionData -import com.tangem.blockchain.common.TransactionSender +import com.tangem.blockchain.common.* import com.tangem.blockchain.extensions.* import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes +import com.tangem.crypto.CryptoUtils import com.tangem.operations.sign.SignHashCommand import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.toFormattedString @@ -36,9 +34,14 @@ class WalletConnectSdkHelper { type: WcTransactionType, ): WcTransactionData? { val factory = store.state.globalState.tapWalletManager.walletManagerFactory + val publicKey = Wallet.PublicKey( + session.wallet.walletPublicKey, + session.wallet.derivedPublicKey, + session.wallet.derivationPath, + ) val walletManager = factory.makeEthereumWalletManager( session.wallet.cardId, - session.wallet.walletPublicKey.hexToBytes(), + publicKey, emptyList(), isTestNet = session.wallet.isTestNet ) ?: return null @@ -151,7 +154,8 @@ class WalletConnectSdkHelper { val command = SignHashCommand( hash = dataToSign.hash, - walletPublicKey = data.walletManager.wallet.publicKey + walletPublicKey = data.walletManager.wallet.publicKey.seedKey, + derivationPath = data.walletManager.wallet.publicKey.derivationPath ) val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message()) return when (result) { @@ -208,13 +212,13 @@ class WalletConnectSdkHelper { } suspend fun signPersonalMessage(hashToSign: ByteArray, wallet: WalletForSession): String? { - val publicKey = wallet.walletPublicKey.hexToBytes() - val command = SignHashCommand(hashToSign, publicKey) + val key = wallet.derivedPublicKey ?: wallet.walletPublicKey + val command = SignHashCommand(hashToSign, wallet.walletPublicKey, wallet.derivationPath) return when (val result = tangemSdkManager.runTaskAsync(command, wallet.cardId)) { is CompletionResult.Success -> { val hash = result.data.signature return EthereumUtils.prepareSignedMessageData( - hash, hashToSign, publicKey + hash, hashToSign, CryptoUtils.decompressPublicKey(key) ) } is CompletionResult.Failure -> { diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index cdbde3bb92..71f4bd5fd7 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -184,16 +184,11 @@ private fun handleSecurityAction( private fun prepareAllowedSecurityOptions( card: Card?, currentSecurityOption: SecurityOption?, ): EnumSet { - val prohibitDefaultPin = card?.settings?.isResettingUserCodesAllowed != true - - val allowedSecurityOptions = EnumSet.noneOf(SecurityOption::class.java) + val allowedSecurityOptions = EnumSet.of(SecurityOption.LongTap) if (card?.isTangemTwin() == true) { allowedSecurityOptions.add(SecurityOption.PassCode) } - if ((currentSecurityOption == SecurityOption.LongTap) || !prohibitDefaultPin) { - allowedSecurityOptions.add(SecurityOption.LongTap) - } if (currentSecurityOption == SecurityOption.AccessCode) { allowedSecurityOptions.add(SecurityOption.AccessCode) } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index dff6c35c72..2c1aab1596 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -1,9 +1,7 @@ package com.tangem.tap.features.details.redux.walletconnect import com.tangem.blockchain.common.* -import com.tangem.common.card.Card import com.tangem.common.extensions.guard -import com.tangem.common.extensions.toHexString import com.tangem.tap.* import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.getFromClipboard @@ -14,6 +12,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapWorkarounds.isTestCard import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.isMultiwalletAllowed +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.domain.walletconnect.WalletConnectManager import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.wallet.R @@ -128,24 +127,32 @@ class WalletConnectMiddleware { } private fun handleScanCard(wcUri: String) { - store.dispatch(GlobalAction.ScanCard({ scanResponse -> + store.dispatch(GlobalAction.ScanCard(true, { scanResponse -> val card = scanResponse.card if (!card.isMultiwalletAllowed) { store.dispatchOnMain(WalletConnectAction.UnsupportedCard) return@ScanCard } - val walletManager = getWalletManager(card).guard { + val walletManager = getWalletManager(scanResponse).guard { store.dispatchOnMain(WalletConnectAction.UnsupportedCard) return@ScanCard } - val key = walletManager.wallet.publicKey + val wallet = walletManager.wallet + val derivedKey = if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) { + null + } else { + walletManager.wallet.publicKey.blockchainKey + } store.dispatchOnMain(WalletConnectAction.OpenSession( wcUri = wcUri, wallet = WalletForSession( - card.cardId, key.toHexString(), - isTestNet = card.isTestCard + card.cardId, + wallet.publicKey.seedKey, + derivedKey, + wallet.publicKey.derivationPath, + card.isTestCard ), )) }, { @@ -153,26 +160,32 @@ class WalletConnectMiddleware { }, R.string.wallet_connect_scan_card_message)) } - private fun getWalletManager(card: Card): WalletManager? { + private fun getWalletManager(scanResponse: ScanResponse): WalletManager? { + val card = scanResponse.card val factory = store.state.globalState.tapWalletManager.walletManagerFactory + val wcBlockchain = if (scanResponse.card.isTestCard) { + Blockchain.EthereumTestnet + } else { + Blockchain.Ethereum + } + return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) { - store.state.walletState.getWalletManager(Blockchain.Ethereum) - ?: factory.makeWalletManagerForApp(card, Blockchain.Ethereum) + store.state.walletState.getWalletManager(wcBlockchain) + ?: factory.makeWalletManagerForApp(scanResponse, wcBlockchain) ?.also { walletManager -> store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain)) } } else { if (currenciesRepository.loadCardCurrencies(card.cardId)?.blockchains?.contains( - Blockchain.Ethereum) == true + wcBlockchain) == true ) { - factory.makeWalletManagerForApp(card, Blockchain.Ethereum) + factory.makeWalletManagerForApp(scanResponse, wcBlockchain) } else { - factory.makeWalletManagerForApp(card, - Blockchain.Ethereum) + factory.makeWalletManagerForApp(scanResponse, wcBlockchain) ?.also { - currenciesRepository.saveAddedBlockchain(card.cardId, Blockchain.Ethereum) + currenciesRepository.saveAddedBlockchain(card.cardId, wcBlockchain) } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt index 38aeaabc84..2757d8cea9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt @@ -1,7 +1,9 @@ package com.tangem.tap.features.details.redux.walletconnect +import com.squareup.moshi.JsonClass import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.WalletManager +import com.tangem.common.hdWallet.DerivationPath import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData @@ -21,9 +23,12 @@ data class WalletConnectSession( val peerMeta: WCPeerMeta, ) +@JsonClass(generateAdapter = true) data class WalletForSession( val cardId: String, - val walletPublicKey: String, + val walletPublicKey: ByteArray, + val derivedPublicKey: ByteArray?, + val derivationPath: DerivationPath?, val isTestNet: Boolean = false, ) { val chainId diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 0dc437eb3c..b93b3a2e7e 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -27,8 +27,7 @@ class HomeMiddleware { companion object { val handler = homeMiddleware - const val CARD_SHOP_URI = - "https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate" + const val CARD_SHOP_URI = "http://cards.tangem.com/" } } @@ -61,7 +60,7 @@ private fun handleReadCard() { store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) } else { changeButtonState(ButtonState.PROGRESS) - store.dispatch(GlobalAction.ScanCard({ scanResponse -> + store.dispatch(GlobalAction.ScanCard(false, { scanResponse -> store.state.globalState.tapWalletManager.updateConfigManager(scanResponse) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index 558f5b0e5f..36e9c93cc1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -42,7 +42,7 @@ private val onboardingNoteMiddleware: Middleware = { dispatch, state - private fun handleNoteAction(action: Action, dispatch: DispatchFunction) { if (action !is OnboardingNoteAction) return val globalState = store.state.globalState - val onboardingManager = globalState.onboardingManager ?: return + val onboardingManager = globalState.onboardingState.onboardingManager ?: return val scanResponse = onboardingManager.scanResponse val card = onboardingManager.scanResponse.card diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt index 4606c539ae..f3419e67fa 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt @@ -37,7 +37,7 @@ private val onboardingOtherCardsMiddleware: Middleware = { dispatch, s private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { if (action !is OnboardingOtherCardsAction) return val globalState = store.state.globalState - val onboardingManager = globalState.onboardingManager ?: return + val onboardingManager = globalState.onboardingState.onboardingManager ?: return val card = onboardingManager.scanResponse.card diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 818b1974aa..f69e59381f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -45,7 +45,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { val action = action as? TwinCardsAction ?: return val globalState = store.state.globalState - val onboardingManager = globalState.onboardingManager + val onboardingManager = globalState.onboardingState.onboardingManager val twinCardsState = store.state.twinCardsState fun getScanResponse(): ScanResponse { 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 364f97ce71..b3056d7bdd 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 @@ -51,6 +51,7 @@ sealed class BackupAction : Action { data class SaveFirstAccessCode(val accessCode: String) : BackupAction() object ShowReenterAccessCodeScreen : BackupAction() data class SaveAccessCodeConfirmation(val accessCodeConfirmation: String) : BackupAction() + object OnAccessCodeDialogClosed : BackupAction() data class PrepareToWriteBackupCard(val cardNumber: Int) : BackupAction() data class WriteBackupCard(val cardNumber: Int) : BackupAction() @@ -60,6 +61,7 @@ sealed class BackupAction : Action { object FinishBackup : BackupAction() object DiscardBackup : BackupAction() + object DiscardSavedBackup : BackupAction() object ResumeBackup : 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 f0c695ebd0..f9465751c3 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 @@ -12,9 +12,10 @@ 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.extensions.hasWallets +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.home.redux.HomeAction -import com.tangem.tap.features.home.redux.HomeMiddleware import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction +import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware.Companion.BUY_WALLET_URL import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware @@ -22,6 +23,8 @@ import org.rekotlin.Middleware class OnboardingWalletMiddleware { companion object { val handler = onboardingWalletMiddleware + + const val BUY_WALLET_URL = "https://wallet.tangem.com/" } } @@ -37,7 +40,7 @@ private val onboardingWalletMiddleware: Middleware = { dispatch, state private fun handleWalletAction(action: Action) { if (action !is OnboardingWalletAction) return val globalState = store.state.globalState - val onboardingManager = globalState.onboardingManager + val onboardingManager = globalState.onboardingState.onboardingManager val scanResponse = onboardingManager?.scanResponse val card = scanResponse?.card @@ -71,7 +74,9 @@ private fun handleWalletAction(action: Action) { withMainContext { when (result) { is CompletionResult.Success -> { - val updatedResponse = scanResponse.copy(card = result.data) + //here we must use updated scanResponse after createWallet & derivation + val updatedResponse = + globalState.onboardingState.onboardingManager.scanResponse.copy(card = result.data) onboardingManager.scanResponse = updatedResponse onboardingManager.activationStarted(updatedResponse.card.cardId) store.dispatch(OnboardingWalletAction.ProceedBackup) @@ -86,157 +91,192 @@ private fun handleWalletAction(action: Action) { OnboardingWalletAction.FinishOnboarding -> { store.dispatch(GlobalAction.Onboarding.Stop) - (listOf(walletState.backupState.primaryCardId) + - walletState.backupState.backupCardIds + scanResponse?.card?.cardId) - .distinct().filterNotNull() - .forEach { cardId -> - preferencesStorage.usedCardsPrefStorage.activationFinished(cardId) - } - if (scanResponse == null) { store.dispatch(NavigationAction.PopBackTo()) store.dispatch(HomeAction.ReadCard) } else { - scope.launch { globalState.tapWalletManager.onCardScanned(scanResponse) } + val backupState = store.state.onboardingWalletState.backupState + val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState) + scope.launch { globalState.tapWalletManager.onCardScanned(updatedScanResponse) } store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) } } OnboardingWalletAction.ProceedBackup -> { val newAction = when (val backupState = backupService.currentState) { - BackupService.State.Preparing -> BackupAction.IntroduceBackup BackupService.State.FinalizingPrimaryCard -> BackupAction.PrepareToWritePrimaryCard is BackupService.State.FinalizingBackupCard -> BackupAction.PrepareToWriteBackupCard(backupState.index) - BackupService.State.Finished -> BackupAction.FinishBackup + else -> BackupAction.IntroduceBackup } store.dispatch(newAction) } OnboardingWalletAction.OnBackPressed -> { - if (walletState.backupState.backupStep is BackupStep.WriteBackupCard || - walletState.backupState.backupStep is BackupStep.WritePrimaryCard) { - store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress)) - } else { - store.dispatch(NavigationAction.PopBackTo()) + when (walletState.backupState.backupStep) { + BackupStep.InitBackup, BackupStep.Finished -> store.dispatch(NavigationAction.PopBackTo()) + BackupStep.ScanOriginCard, BackupStep.AddBackupCards, BackupStep.EnterAccessCode, + BackupStep.ReenterAccessCode, BackupStep.SetAccessCode, BackupStep.WritePrimaryCard, + -> { + store.dispatch(BackupAction.DiscardBackup) + store.dispatch(NavigationAction.PopBackTo()) + } + is BackupStep.WriteBackupCard -> + store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress)) } } } } +private fun updateScanResponseAfterBackup( + scanResponse: ScanResponse, backupState: BackupState, +): ScanResponse { + val card = if (backupState.backupCardsNumber > 0) { + val cardsCount = backupState.backupCardsNumber + scanResponse.card.copy( + backupStatus = Card.BackupStatus.Active(cardCount = cardsCount), + isAccessCodeSet = true + ) + } else { + scanResponse.card + } + return scanResponse.copy(card = card) +} + class BackupMiddleware { val backupMiddleware: Middleware = { dispatch, state -> { next -> { action -> - val backupState = state()?.onboardingWalletState?.backupState - when (action) { - is BackupAction.StartBackup -> { - backupService.discardSavedBackup() - } - is BackupAction.ScanPrimaryCard -> { - backupService.readPrimaryCard { result -> - when (result) { - is CompletionResult.Success -> { - store.dispatchOnMain(BackupAction.StartAddingBackupCards) - } - is CompletionResult.Failure -> { - } - } - } - } - is BackupAction.AddBackupCard -> { - backupService.addBackupCard { result -> - when (result) { - is CompletionResult.Success -> { - store.dispatchOnMain(BackupAction.AddBackupCard.Success) - } - is CompletionResult.Failure -> { - - } - } - } - } - is BackupAction.GoToShop -> { - store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI) - } - is BackupAction.FinishAddingBackupCards -> { - if (backupService.addedBackupCardsCount == 1) { - store.dispatchOnMain(GlobalAction.ShowDialog(BackupDialog.BuyMoreBackupCards)) - } - if (backupService.addedBackupCardsCount == 2) { - store.dispatchOnMain(BackupAction.ShowAccessCodeInfoScreen) - } - } - is BackupAction.CheckAccessCode -> { - if (action.accessCode.length < 4) { - store.dispatch(BackupAction.SetAccessCodeError( - AccessCodeError.CodeTooShort - )) - } else { - store.dispatch(BackupAction.SaveFirstAccessCode(action.accessCode)) - } - } - is BackupAction.SaveAccessCodeConfirmation -> { - if (action.accessCodeConfirmation == backupState?.accessCode) { - backupService.setAccessCode(action.accessCodeConfirmation) - store.dispatch(BackupAction.PrepareToWritePrimaryCard) - } else { - store.dispatch(BackupAction.SetAccessCodeError( - AccessCodeError.CodesDoNotMatch - )) - } - } - is BackupAction.WritePrimaryCard -> { - backupService.proceedBackup { result -> - when (result) { - is CompletionResult.Success -> { - store.dispatchOnMain( - BackupAction.PrepareToWriteBackupCard(1) - ) - } - is CompletionResult.Failure -> { - } - } - } - } - is BackupAction.WriteBackupCard -> { - backupService.proceedBackup { result -> - when (result) { - is CompletionResult.Success -> { - if (backupService.currentState == BackupService.State.Finished) { - store.dispatchOnMain(BackupAction.FinishBackup) - } else { - store.dispatchOnMain( - BackupAction.PrepareToWriteBackupCard(action.cardNumber + 1) - ) - } - } - is CompletionResult.Failure -> { - - } - } - } - } - is BackupAction.FinishBackup -> { -// store.dispatch(OnboardingWalletAction.Done) - } - is BackupAction.DiscardBackup -> { - backupService.discardSavedBackup() - } - is BackupAction.CheckForUnfinishedBackup -> { - if (backupService.hasIncompletedBackup) { - store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound)) - } - } - is BackupAction.ResumeBackup -> { - store.dispatch(OnboardingWalletAction.ProceedBackup) - store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) - } - is BackupAction.DismissBackup -> { - store.dispatch(BackupAction.FinishBackup) - } - } - + if (action is BackupAction) handleBackupAction(action) next(action) } } } +} + +private fun handleBackupAction(action: BackupAction) { + val backupState = store.state.onboardingWalletState.backupState + + val globalState = store.state.globalState + val onboardingManager = globalState.onboardingState.onboardingManager + + val scanResponse = onboardingManager?.scanResponse + val card = scanResponse?.card + + when (action) { + is BackupAction.StartBackup -> { + backupService.discardSavedBackup() + } + is BackupAction.ScanPrimaryCard -> { + backupService.readPrimaryCard(cardId = card?.cardId) { result -> + when (result) { + is CompletionResult.Success -> { + store.dispatchOnMain(BackupAction.StartAddingBackupCards) + } + is CompletionResult.Failure -> { + } + } + } + } + is BackupAction.AddBackupCard -> { + backupService.addBackupCard { result -> + when (result) { + is CompletionResult.Success -> { + store.dispatchOnMain(BackupAction.AddBackupCard.Success) + } + is CompletionResult.Failure -> { + + } + } + } + } + is BackupAction.GoToShop -> { + store.dispatchOpenUrl(BUY_WALLET_URL) + } + is BackupAction.FinishAddingBackupCards -> { + if (backupService.addedBackupCardsCount == 1) { + store.dispatchOnMain(GlobalAction.ShowDialog(BackupDialog.AddMoreBackupCards)) + } + if (backupService.addedBackupCardsCount == 2) { + store.dispatchOnMain(BackupAction.ShowAccessCodeInfoScreen) + } + } + is BackupAction.CheckAccessCode -> { + if (action.accessCode.length < 4) { + store.dispatch(BackupAction.SetAccessCodeError( + AccessCodeError.CodeTooShort + )) + } else { + store.dispatch(BackupAction.SaveFirstAccessCode(action.accessCode)) + } + } + is BackupAction.SaveAccessCodeConfirmation -> { + if (action.accessCodeConfirmation == backupState.accessCode) { + backupService.setAccessCode(action.accessCodeConfirmation) + store.dispatch(BackupAction.PrepareToWritePrimaryCard) + } else { + store.dispatch(BackupAction.SetAccessCodeError( + AccessCodeError.CodesDoNotMatch + )) + } + } + is BackupAction.WritePrimaryCard -> { + backupService.proceedBackup { result -> + when (result) { + is CompletionResult.Success -> { + store.dispatchOnMain( + BackupAction.PrepareToWriteBackupCard(1) + ) + } + is CompletionResult.Failure -> { + } + } + } + } + is BackupAction.WriteBackupCard -> { + backupService.proceedBackup { result -> + when (result) { + is CompletionResult.Success -> { + if (backupService.currentState == BackupService.State.Finished) { + store.dispatchOnMain(BackupAction.FinishBackup) + } else { + store.dispatchOnMain( + BackupAction.PrepareToWriteBackupCard(action.cardNumber + 1) + ) + } + } + is CompletionResult.Failure -> { + + } + } + } + } + is BackupAction.FinishBackup -> { + (listOf(backupState.primaryCardId, card?.cardId) + backupState.backupCardIds) + .distinct().filterNotNull() + .forEach { cardId -> + preferencesStorage.usedCardsPrefStorage.activationFinished(cardId) + } + } + is BackupAction.DiscardBackup -> { + backupService.discardSavedBackup() + } + is BackupAction.DiscardSavedBackup -> { + backupService.primaryCardId?.let { + preferencesStorage.usedCardsPrefStorage.activationFinished(it) + } + backupService.discardSavedBackup() + } + is BackupAction.CheckForUnfinishedBackup -> { + if (backupService.hasIncompletedBackup) { + store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound)) + } + } + is BackupAction.ResumeBackup -> { + store.dispatch(GlobalAction.Onboarding.Start(null, true)) + store.dispatch(OnboardingWalletAction.ProceedBackup) + store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) + } + is BackupAction.DismissBackup -> { + store.dispatch(BackupAction.FinishBackup) + } + } } \ No newline at end of file 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 4069f04a22..3947cd5d3f 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 @@ -39,7 +39,10 @@ class BackupReducer { ): BackupState { return when (action) { - BackupAction.IntroduceBackup -> state.copy(backupStep = BackupStep.InitBackup) + BackupAction.IntroduceBackup -> BackupState( + backupStep = BackupStep.InitBackup, + canSkipBackup = state.canSkipBackup + ) BackupAction.StartBackup -> state.copy(backupStep = BackupStep.ScanOriginCard) @@ -104,6 +107,10 @@ class BackupReducer { ) } + BackupAction.OnAccessCodeDialogClosed -> { + state.copy(backupStep = BackupStep.AddBackupCards) + } + BackupAction.WritePrimaryCard -> state is BackupAction.WriteBackupCard -> state is BackupAction.SaveAccessCodeConfirmation -> state @@ -119,6 +126,7 @@ class BackupReducer { BackupAction.CheckForUnfinishedBackup -> state BackupAction.DiscardBackup -> state BackupAction.ResumeBackup -> state + BackupAction.DiscardSavedBackup -> state } } 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 6c6ff6d1f7..d8d0c687b7 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 @@ -74,7 +74,7 @@ sealed class BackupStep { } sealed class BackupDialog: StateDialog { - object BuyMoreBackupCards : StateDialog + object AddMoreBackupCards : StateDialog object BackupInProgress : StateDialog object UnfinishedBackupFound : StateDialog object ConfirmDiscardingBackup : StateDialog 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 52adf8a47d..f415e176b9 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,20 +10,19 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.tabs.TabLayoutMediator import com.tangem.common.CardIdFormatter import com.tangem.common.core.CardIdDisplayFormat -import com.tangem.tap.common.extensions.addOnBackPressedDispatcher import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget import com.tangem.tap.common.postUi -import com.tangem.tap.features.home.redux.HomeMiddleware +import com.tangem.tap.features.FragmentOnBackPressedHandler +import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.onboarding.products.wallet.redux.* +import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware.Companion.BUY_WALLET_URL import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_onboarding_wallet.* -import kotlinx.android.synthetic.main.fragment_onboarding_wallet.toolbar -import kotlinx.android.synthetic.main.fragment_wallet_details.* import kotlinx.android.synthetic.main.layout_onboarding_buttons_add_cards.* import kotlinx.android.synthetic.main.layout_onboarding_buttons_common.* import kotlinx.android.synthetic.main.view_confetti.* @@ -31,7 +30,7 @@ import kotlinx.android.synthetic.main.view_onboarding_progress.* import org.rekotlin.StoreSubscriber class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), - StoreSubscriber { + StoreSubscriber, FragmentOnBackPressedHandler { private var accessCodeDialog: AccessCodeDialog? = null private lateinit var cardsWidget: BackupCardsWidget @@ -41,11 +40,6 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), postponeEnterTransition() setHasOptionsMenu(true) - - activity?.addOnBackPressedDispatcher { - store.dispatch(OnboardingWalletAction.OnBackPressed) - } - val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.fade) exitTransition = inflater.inflateTransition(R.transition.fade) @@ -68,7 +62,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), store.dispatch(OnboardingWalletAction.Init) toolbar.setNavigationOnClickListener { activity?.onBackPressed() } -// store.dispatch(OnboardingWalletAction.LoadArtwork) + addBackPressHandler(this) } override fun onStart() { @@ -181,6 +175,9 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), imv_first_backup_card.show() imv_second_backup_card.show() + accessCodeDialog?.dismiss() + accessCodeDialog = null + layout_buttons_add_cards.show() layout_buttons_common.hide() if (state.backupCardsNumber < state.maxBackupCards) { @@ -224,7 +221,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), dismissWithAnimation = true create() setOnCancelListener { - + store.dispatch(BackupAction.OnAccessCodeDialogClosed) } show() showInfoScreen() @@ -366,7 +363,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.shop_menu -> { - store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI) + store.dispatchOpenUrl(BUY_WALLET_URL) true } else -> super.onOptionsItemSelected(item) @@ -381,4 +378,8 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), backupStep == BackupStep.ScanOriginCard || backupStep == BackupStep.AddBackupCards menu.getItem(0).isVisible = shopMenuShouldBeVisible } + + override fun handleOnBackPressed() { + store.dispatch(OnboardingWalletAction.OnBackPressed) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt index afd4521ac1..9433d4e274 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt @@ -33,6 +33,7 @@ class AccessCodeDialog(context: Context) : BottomSheetDialog(context) { fun showInfoScreen() { behavior.state = BottomSheetBehavior.STATE_EXPANDED + access_code_title.text = context.getText(R.string.onboarding_access_code_intro_title) layout_backup_access_code_info.show() layout_backup_access_code_submit.hide() btn_access_code_create.setOnClickListener { @@ -43,6 +44,7 @@ class AccessCodeDialog(context: Context) : BottomSheetDialog(context) { fun showEnterAccessCode() { layout_backup_access_code_info.hide() layout_backup_access_code_submit.show() + access_code_title.text = context.getText(R.string.onboarding_access_code_intro_title) btn_access_code_submit.text = context.getText(R.string.common_continue) btn_access_code_submit.setOnClickListener { store.dispatch(BackupAction.CheckAccessCode(et_access_code.text.toString())) @@ -52,6 +54,7 @@ class AccessCodeDialog(context: Context) : BottomSheetDialog(context) { fun showReenterAccessCode() { layout_backup_access_code_info.hide() layout_backup_access_code_submit.show() + access_code_title.text = context.getText(R.string.onboarding_access_code_repeat_code_title) et_access_code.setText("") btn_access_code_submit.text = context.getText(R.string.common_submit) btn_access_code_submit.setOnClickListener { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BuyMoreBackupCardsDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt similarity index 89% rename from app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BuyMoreBackupCardsDialog.kt rename to app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt index 06f4c1565d..883ce390dc 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BuyMoreBackupCardsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt @@ -7,7 +7,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.store import com.tangem.wallet.R -class BuyMoreBackupCardsDialog { +class AddMoreBackupCardsDialog { companion object { fun create(context: Context): AlertDialog { return AlertDialog.Builder(context).apply { @@ -16,8 +16,8 @@ class BuyMoreBackupCardsDialog { setPositiveButton(R.string.common_continue) { _, _ -> store.dispatch(BackupAction.ShowAccessCodeInfoScreen) } - setNegativeButton(R.string.onboarding_button_buy_more_cards) { _, _ -> - store.dispatch(BackupAction.GoToShop) + setNegativeButton(R.string.onboarding_button_add_more_cards) { _, _ -> + } setOnDismissListener { store.dispatch(GlobalAction.HideDialog) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt index 07900822ba..8b88890be8 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt @@ -17,7 +17,7 @@ class ConfirmDiscardingBackupDialog { store.dispatch(BackupAction.ResumeBackup) } setNegativeButton(R.string.welcome_interrupted_backup_discard_discard) { _, _ -> - store.dispatch(BackupAction.DiscardBackup) + store.dispatch(BackupAction.DiscardSavedBackup) } setOnDismissListener { store.dispatch(GlobalAction.HideDialog) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 6701ac636d..ff45a74e19 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -15,6 +15,7 @@ import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.domain.TangemSigner import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TapWorkarounds.isStart2Coin @@ -152,21 +153,32 @@ private fun sendTransaction( if (card.isStart2Coin) { tangemSdk.config.linkedTerminal = false } - val signer = TangemSigner( - tangemSdk = tangemSdk, initialMessage = action.messageForSigner - ) { signResponse -> + + val signer = TangemSigner(tangemSdk, action.messageForSigner) { signResponse -> store.dispatch( GlobalAction.UpdateWalletSignedHashes( walletSignedHashes = signResponse.totalSignedHashes, - walletPublicKey = walletManager.wallet.publicKey, + walletPublicKey = walletManager.wallet.publicKey.seedKey, remainingSignatures = signResponse.remainingSignatures ) ) } - val result = (walletManager as TransactionSender).send(txData, signer) + + val sendResult = try { + (walletManager as TransactionSender).send(txData, signer) + } catch (ex: Exception) { + FirebaseCrashlytics.getInstance().recordException(ex) + delay(DELAY_SDK_DIALOG_CLOSE) + withMainContext { + dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) + store.dispatchErrorNotification(TapError.CustomError(ex.localizedMessage ?: "Unknown error")) + } + return@launch + } + withContext(Dispatchers.Main) { tangemSdk.config.linkedTerminal = isLinkedTerminal - when (result) { + when (sendResult) { is SimpleResult.Success -> { FirebaseAnalyticsHandler.triggerEvent( event = AnalyticsEvent.TRANSACTION_IS_SENT, @@ -191,20 +203,18 @@ private fun sendTransaction( } } is SimpleResult.Failure -> { - when (result.error) { + when (sendResult.error) { is CreateAccountUnderfunded -> { - val error = result.error as CreateAccountUnderfunded + val error = sendResult.error as CreateAccountUnderfunded val reserve = error.minReserve.value?.stripZeroPlainString() ?: "0" val symbol = error.minReserve.currencySymbol dispatch(SendAction.SendError(TapError.CreateAccountUnderfunded(listOf(reserve, symbol)))) } is SendException -> { - result.error?.let { - FirebaseCrashlytics.getInstance().recordException(it) - } + sendResult.error?.let { FirebaseCrashlytics.getInstance().recordException(it) } } is Throwable -> { - val throwable = result.error as Throwable + val throwable = sendResult.error as Throwable val message = throwable.message val infoHolder = store.state.globalState.feedbackManager?.infoHolder when { @@ -228,13 +238,13 @@ private fun sendTransaction( dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated)) } else -> { - (result.error as? TangemSdkError)?.let { error -> + (sendResult.error as? TangemSdkError)?.let { error -> FirebaseAnalyticsHandler.logCardSdkError( error, FirebaseAnalyticsHandler.ActionToLog.SendTransaction, mapOf( FirebaseAnalyticsHandler.AnalyticsParam.BLOCKCHAIN - to walletManager.wallet.blockchain.currency), + to walletManager.wallet.blockchain.currency), card = card, ) } @@ -303,7 +313,7 @@ fun createValidateTransactionError(errorList: EnumSet, walletM private fun setIfSendingToPayIdEnabled(appState: AppState?, dispatch: (Action) -> Unit) { val isSendingToPayIdEnabled = - appState?.globalState?.configManager?.config?.isSendingToPayIdEnabled ?: false + appState?.globalState?.configManager?.config?.isSendingToPayIdEnabled ?: false dispatch(AddressPayIdActionUi.ChangePayIdState(isSendingToPayIdEnabled)) } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt index 98efd84540..bcf0bf5644 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt @@ -6,8 +6,12 @@ import com.tangem.blockchain.common.Token import com.tangem.wallet.R sealed class CurrencyListItem { + var isAdded: Boolean = false + var isLock: Boolean = false + data class TokenListItem(val token: Token) : CurrencyListItem() data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem() + data class TitleListItem( @StringRes val titleResId: Int, var isContentShown: Boolean = true, diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt index 1be9e7c701..d2b911d8b7 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt @@ -1,22 +1,20 @@ package com.tangem.tap.features.tokens.redux import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Token import com.tangem.tap.features.wallet.redux.WalletData import org.rekotlin.Action sealed class TokensAction : Action { + object ResetState : TokensAction() + object LoadCurrencies : TokensAction() { data class Success(val currencies: List) : TokensAction() } - object LoadCardTokens : TokensAction() { - data class Success(val tokens: List) : TokensAction() - } - data class SetAddedCurrencies(val wallets: List) : TokensAction() data class ToggleShowTokensForBlockchain(val isShown: Boolean, val blockchain: Blockchain) : TokensAction() + data class SaveChanges(val addedItems: List) : TokensAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 6ecff3f595..2176e4ea51 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -1,9 +1,25 @@ package com.tangem.tap.features.tokens.redux +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Token +import com.tangem.common.CompletionResult +import com.tangem.common.card.EllipticCurve +import com.tangem.tap.common.extensions.dispatchErrorNotification +import com.tangem.tap.common.extensions.toMapKey import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.currenciesRepository +import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE +import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TapWorkarounds.isTestCard +import com.tangem.tap.domain.tasks.product.ScanResponse +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import org.rekotlin.Middleware class TokensMiddleware { @@ -12,22 +28,111 @@ class TokensMiddleware { { next -> { action -> when (action) { - is TokensAction.LoadCurrencies -> { - val card = state()?.globalState?.scanResponse?.card - val isTestcard = card?.isTestCard ?: false - val tokens = currenciesRepository.getPopularTokens(isTestcard) - val blockchains = currenciesRepository.getBlockchains( - cardFirmware = card?.firmwareVersion, - isTestNet = isTestcard - ) - val currencies = CurrencyListItem.createListOfCurrencies( - blockchains, tokens - ) - store.dispatch(TokensAction.LoadCurrencies.Success(currencies)) - } + is TokensAction.LoadCurrencies -> handleLoadCurrencies(action) + is TokensAction.SaveChanges -> handleSaveChanges(action) } next(action) } } } + + private fun handleLoadCurrencies(action: TokensAction.LoadCurrencies) { + val scanResponse = store.state.globalState.scanResponse ?: return + val isTestcard = scanResponse.card.isTestCard + + val tokens = currenciesRepository.getPopularTokens(isTestcard) + val blockchains = currenciesRepository.getBlockchains( + cardFirmware = scanResponse.card.firmwareVersion, + isTestNet = isTestcard + ) + val currencies = CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList() + if (scanResponse.isTangemWallet()) { + currencies.forEach { + when (it) { + is CurrencyListItem.TitleListItem -> { + } + is CurrencyListItem.BlockchainListItem -> { + val firstCurve = it.blockchain.getSupportedCurves()?.firstOrNull() + if (firstCurve == EllipticCurve.Ed25519) { + it.isLock = true + } + } + is CurrencyListItem.TokenListItem -> { + val firstCurve = it.token.blockchain.getSupportedCurves()?.firstOrNull() + if (firstCurve == EllipticCurve.Ed25519) { + it.isLock = true + } + } + } + } + } + store.dispatch(TokensAction.LoadCurrencies.Success(currencies)) + } + + private fun handleSaveChanges(action: TokensAction.SaveChanges) { + val scanResponse = store.state.globalState.scanResponse ?: return + + val candidatesToAdd = action.addedItems + if (candidatesToAdd.isEmpty()) return + + val blockchains = candidatesToAdd.filterIsInstance() + .map { it.blockchain } + val tokens = candidatesToAdd.filterIsInstance() + .map { it.token } + if (blockchains.isEmpty() && tokens.isEmpty()) return + + if (scanResponse.isTangemWallet()) { + deriveMissingBlockchains(scanResponse, blockchains, tokens) + } else { + submitAdd(blockchains, tokens) + store.dispatch(NavigationAction.PopBackTo()) + } + } + + private fun deriveMissingBlockchains( + scanResponse: ScanResponse, + blockchains: List, + tokens: List + ) { + val wallet = scanResponse.card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: return + + val tokenBlockchains = tokens.map { it.blockchain } + val derivationPathsCandidates = (blockchains + tokenBlockchains).distinct() + .mapNotNull { it.derivationPath() } + + val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() + val alreadyDerivedKeys = scanResponse.derivedKeys[mapKeyOfWalletPublicKey]?.toMutableList() ?: mutableListOf() + val alreadyDerivedPaths = alreadyDerivedKeys.map { it.derivationPath } + + val toDerive = derivationPathsCandidates.filterNot { alreadyDerivedPaths.contains(it) } + + scope.launch { + val result = tangemSdkManager.derivePublicKeys(scanResponse.card.cardId, wallet.publicKey, toDerive) + when (result) { + is CompletionResult.Success -> { + val newDerivedKeys = result.data + alreadyDerivedKeys.addAll(newDerivedKeys) + val updatedScanResponse = scanResponse.copy( + derivedKeys = mapOf(mapKeyOfWalletPublicKey to alreadyDerivedKeys.toList()) + ) + store.dispatch(GlobalAction.SaveScanNoteResponse(updatedScanResponse)) + submitAdd(blockchains, tokens) + + delay(DELAY_SDK_DIALOG_CLOSE) + store.dispatch(NavigationAction.PopBackTo()) + } + is CompletionResult.Failure -> { + store.dispatchErrorNotification(TapError.CustomError("Error adding tokens")) + } + } + } + } + + private fun submitAdd(blockchains: List, tokens: List) { + (blockchains.map { + WalletAction.MultiWallet.AddBlockchain(it) + } + tokens.map { + WalletAction.MultiWallet.AddToken(it) + }).forEach { store.dispatch(it) } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt index f221424d67..2fb4c5dec8 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt @@ -17,28 +17,23 @@ private fun internalReduce(action: Action, state: AppState): TokensState { val tokensState = state.tokensState return when (action) { + is TokensAction.ResetState -> TokensState() is TokensAction.LoadCurrencies.Success -> { tokensState.copy(currencies = action.currencies, shownCurrencies = action.currencies) } is TokensAction.SetAddedCurrencies -> { tokensState.copy(addedCurrencies = action.wallets.toCardCurrencies()) } - is TokensAction.LoadCardTokens.Success -> { - tokensState.copy(addedTokens = LinkedHashSet( - action.tokens.map { TokenWithAmount(it, null) } - )) - } - is TokensAction.ToggleShowTokensForBlockchain -> { if (action.isShown) { val shownCurrencies = tokensState.shownCurrencies .removeTokensForBlockchain(action.blockchain) - shownCurrencies.toggleHeaderContentShownValue(action.blockchain) + shownCurrencies.toggleHeaderContentShownValue(action.blockchain) tokensState.copy(shownCurrencies = shownCurrencies) } else { val shownCurrencies = tokensState.shownCurrencies .addTokensForBlockchain(action.blockchain, tokensState.currencies) - shownCurrencies.toggleHeaderContentShownValue(action.blockchain) + shownCurrencies.toggleHeaderContentShownValue(action.blockchain) tokensState.copy(shownCurrencies = shownCurrencies) } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index b5d09b7074..38de45981b 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -12,6 +12,7 @@ import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.transition.TransitionInflater import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.tokens.redux.TokensAction import com.tangem.tap.features.tokens.redux.TokensState import com.tangem.tap.features.tokens.ui.adapters.CurrenciesAdapter import com.tangem.tap.mainScope @@ -57,30 +58,35 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + (activity as? AppCompatActivity)?.setSupportActionBar(toolbar) toolbar.setNavigationOnClickListener { activity?.onBackPressed() } setupPopularTokensRecyclerView() + btn_tokens_save_changes.setOnClickListener { + store.dispatch(TokensAction.SaveChanges(viewAdapter.getAddedItems())) + } } - private fun setupPopularTokensRecyclerView() { viewAdapter = CurrenciesAdapter() + viewAdapter.setOnItemAddListener { + btn_tokens_save_changes.isEnabled = viewAdapter.getAddedItems().isNotEmpty() + } + rv_popular_tokens.layoutManager = LinearLayoutManager(context) rv_popular_tokens.adapter = viewAdapter } - override fun newState(state: TokensState) { if (activity == null) return + viewAdapter.addedCurrencies = state.addedCurrencies viewAdapter.submitUnfilteredList(state.shownCurrencies) } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { - R.id.menu_search -> { - true - } + R.id.menu_search -> true else -> super.onOptionsItemSelected(item) } } @@ -101,6 +107,11 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), .launchIn(mainScope) return super.onCreateOptionsMenu(menu, inflater); } + + override fun onDestroy() { + store.dispatch(TokensAction.ResetState) + super.onDestroy() + } } fun SearchView.inputtedTextAsFlow(): Flow = callbackFlow { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt index 85a8462b81..b3d0ecdc04 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt @@ -7,6 +7,9 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.squareup.picasso.Picasso +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Token +import com.tangem.common.extensions.VoidCallback import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.loadCurrenciesIcon @@ -14,7 +17,6 @@ import com.tangem.tap.common.extensions.show import com.tangem.tap.domain.tokens.CardCurrencies import com.tangem.tap.features.tokens.redux.CurrencyListItem import com.tangem.tap.features.tokens.redux.TokensAction -import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.item_currency_subtitle.view.* @@ -27,11 +29,28 @@ class CurrenciesAdapter : ListAdapter private var unfilteredList = listOf() + private val currentlyAddedItems = mutableListOf() + + private var vhOnItemAddListener: ((CurrencyListItem) -> Unit) = { + currentlyAddedItems.add(it) + onItemAddListener?.invoke() + } + private var onItemAddListener: VoidCallback? = null + fun submitUnfilteredList(list: List) { unfilteredList = list submitList(list) } + fun setOnItemAddListener(itemAddListener: VoidCallback) { + onItemAddListener = itemAddListener + if (currentlyAddedItems.isNotEmpty()) itemAddListener() + } + + fun getAddedItems(): List { + return currentlyAddedItems.toList() + } + override fun getItemViewType(position: Int): Int { return when (currentList[position]) { is CurrencyListItem.TitleListItem -> 0 @@ -47,11 +66,13 @@ class CurrenciesAdapter : ListAdapter ) 1 -> CurrenciesViewHolder( LayoutInflater.from(parent.context) - .inflate(R.layout.item_popular_token, parent, false) + .inflate(R.layout.item_popular_token, parent, false), + vhOnItemAddListener ) else -> CurrenciesViewHolder( LayoutInflater.from(parent.context) - .inflate(R.layout.item_popular_token, parent, false) + .inflate(R.layout.item_popular_token, parent, false), + vhOnItemAddListener ) } } @@ -61,7 +82,7 @@ class CurrenciesAdapter : ListAdapter if (holder is TitleViewHolder && listItem is CurrencyListItem.TitleListItem) { holder.bind(listItem) } else if (holder is CurrenciesViewHolder) { - holder.bind(listItem, addedCurrencies) + holder.bind(listItem, addedCurrencies, currentlyAddedItems) } } @@ -86,15 +107,15 @@ class CurrenciesAdapter : ListAdapter is CurrencyListItem.BlockchainListItem -> { element.blockchain.currency.toLowerCase(Locale.US) .contains(queryNormalized) || - element.blockchain.fullName.toLowerCase(Locale.US) - .contains(queryNormalized) + element.blockchain.fullName.toLowerCase(Locale.US) + .contains(queryNormalized) } is CurrencyListItem.TokenListItem -> { element.token.name.toLowerCase(Locale.US) .contains(queryNormalized) || - element.token.symbol.toLowerCase(Locale.US) - .contains(queryNormalized) + element.token.symbol.toLowerCase(Locale.US) + .contains(queryNormalized) } is CurrencyListItem.TitleListItem -> true } @@ -105,56 +126,85 @@ class CurrenciesAdapter : ListAdapter submitList(list) } - class CurrenciesViewHolder(val view: View) : - RecyclerView.ViewHolder(view) { - fun bind(currency: CurrencyListItem, addedCurrencies: CardCurrencies?) { + class CurrenciesViewHolder( + private val view: View, + private val onItemAddListener: ((CurrencyListItem) -> Unit), + ) : RecyclerView.ViewHolder(view) { + + fun bind( + currency: CurrencyListItem, + addedCurrencies: CardCurrencies?, + currentlyAddedItems: MutableList + ) { when (currency) { is CurrencyListItem.BlockchainListItem -> { - val blockchain = currency.blockchain - view.tv_currency_name.text = blockchain.fullName - view.tv_currency_symbol.text = blockchain.currency - val isAdded = addedCurrencies?.blockchains?.contains(blockchain) == true - view.btn_add_token.show(!isAdded) - view.btn_token_added.show(isAdded) - - Picasso.get().loadCurrenciesIcon( - imageView = view.iv_currency, - textView = view.tv_token_letter, - blockchain = blockchain, token = null - ) - - view.btn_add_token.setOnClickListener { - store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchain)) - view.btn_add_token.hide() - view.btn_token_added.show() - } + val addedBlockchains = addedCurrencies?.blockchains ?: listOf() + val currentlyAdded = currentlyAddedItems.filterIsInstance() + bindBlockchain(currency, addedBlockchains, currentlyAdded) } is CurrencyListItem.TokenListItem -> { - val token = currency.token - view.tv_currency_name.text = token.name - view.tv_currency_symbol.text = token.symbol - - val isAdded = addedCurrencies?.tokens - ?.any { - it.symbol == token.symbol && it.contractAddress == token.contractAddress - } == true - - view.btn_add_token.show(!isAdded) - view.btn_token_added.show(isAdded) - - Picasso.get().loadCurrenciesIcon( - imageView = view.iv_currency, - textView = view.tv_token_letter, - token = token, blockchain = token.blockchain - ) - view.btn_add_token.setOnClickListener { - store.dispatch(WalletAction.MultiWallet.AddToken(token)) - view.btn_add_token.hide() - view.btn_token_added.show() - } + val addedTokens = addedCurrencies?.tokens ?: listOf() + val currentlyAdded = currentlyAddedItems.filterIsInstance() + bindToken(currency, addedTokens, currentlyAdded) } } } + + private fun bindBlockchain( + currency: CurrencyListItem.BlockchainListItem, + addedBlockchains: List, + currentlyAdded: List + ) { + val blockchain = currency.blockchain + Picasso.get().loadCurrenciesIcon(view.iv_currency, view.tv_token_letter, null, blockchain) + + view.tv_currency_name.text = blockchain.fullName + view.tv_currency_symbol.text = blockchain.currency + view.btn_add_token.setOnClickListener { + onItemAddListener.invoke(currency) + currency.isAdded = true + modifyAddTokenButton(currency) + } + + val isAddedBefore = addedBlockchains.contains(blockchain) + val isCurrentlyAdded = currentlyAdded.any { it.blockchain == blockchain } + currency.isAdded = isAddedBefore || isCurrentlyAdded + modifyAddTokenButton(currency) + } + + private fun bindToken( + currency: CurrencyListItem.TokenListItem, + addedTokens: List, + currentlyAdded: List + ) { + val token = currency.token + Picasso.get().loadCurrenciesIcon(view.iv_currency, view.tv_token_letter, token, token.blockchain) + + view.tv_currency_name.text = token.name + view.tv_currency_symbol.text = token.symbol + view.btn_add_token.setOnClickListener { + onItemAddListener.invoke(currency) + currency.isAdded = true + modifyAddTokenButton(currency) + } + + val isAddedBefore = addedTokens.any { it == token } + val isCurrentlyAdded = currentlyAdded.any { it.token == token } + currency.isAdded = isAddedBefore || isCurrentlyAdded + + modifyAddTokenButton(currency) + } + + private fun modifyAddTokenButton(currency: CurrencyListItem) { + if (currency.isLock) { + view.btn_add_token.setText(R.string.common_add) + view.btn_add_token.isEnabled = false + } else { + val text = if (currency.isAdded) R.string.add_token_added else R.string.common_add + view.btn_add_token.setText(text) + view.btn_add_token.isEnabled = !currency.isAdded + } + } } class TitleViewHolder(val view: View) : diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 149550727b..1081a3303c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -50,8 +50,7 @@ sealed class WalletAction : Action { data class AddToken(val token: Token) : MultiWallet() data class SaveCurrencies(val cardCurrencies: CardCurrencies) : MultiWallet() object FindTokensInUse : MultiWallet() - data class FindBlockchainsInUse(val card: Card, val factory: WalletManagerFactory) : - MultiWallet() + object FindBlockchainsInUse : MultiWallet() data class TokenLoaded(val amount: Amount, val token: Token) : MultiWallet() data class SelectWallet(val walletData: WalletData?) : MultiWallet() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 8397f148e0..11d7232cd3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.* import com.tangem.blockchain.extensions.Result import com.tangem.common.extensions.isZero import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction @@ -24,10 +25,12 @@ class MultiWalletMiddleware { fun handle( action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?, ) { + val globalState = globalState ?: return + val tapWalletManager = globalState.tapWalletManager + when (action) { is WalletAction.MultiWallet.AddWalletManagers -> { - store.state.globalState.feedbackManager?.infoHolder - ?.setWalletsInfo(action.walletManagers) + globalState.feedbackManager?.infoHolder?.setWalletsInfo(action.walletManagers) } is WalletAction.MultiWallet.SelectWallet -> { if (action.walletData != null) { @@ -35,7 +38,7 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.AddToken -> { - globalState?.scanResponse?.card?.cardId?.let { + globalState.scanResponse?.card?.cardId?.let { currenciesRepository.saveAddedToken(it, action.token) } addToken(action.token, walletState, globalState) @@ -44,35 +47,33 @@ class MultiWalletMiddleware { addTokens(action.tokens, walletState, globalState) } is WalletAction.MultiWallet.AddBlockchain -> { - globalState?.scanResponse?.card?.let { card -> - currenciesRepository.saveAddedBlockchain(card.cardId, action.blockchain) + globalState.scanResponse?.let { + currenciesRepository.saveAddedBlockchain(it.card.cardId, action.blockchain) if (walletState?.blockchains?.contains(action.blockchain) != true) { - globalState.tapWalletManager.walletManagerFactory - .makeWalletManagerForApp(card, action.blockchain)?.let { - store.dispatch(WalletAction.MultiWallet.AddWalletManagers(it)) + tapWalletManager.walletManagerFactory + .makeWalletManagerForApp(it, action.blockchain)?.let { walletManager -> + store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager)) } } } store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Blockchain(action.blockchain))) store.dispatch(WalletAction.LoadWallet( - moonpayStatus = globalState?.moonpayStatus, + moonpayStatus = globalState.moonpayStatus, blockchain = action.blockchain - ) + ) ) } is WalletAction.MultiWallet.SaveCurrencies -> { - val cardId = globalState?.scanResponse?.card?.cardId - cardId?.let { currenciesRepository.saveCardCurrencies(it, action.cardCurrencies) } + globalState.scanResponse?.card?.cardId?.let { + currenciesRepository.saveCardCurrencies(it, action.cardCurrencies) + } } is WalletAction.MultiWallet.RemoveWallet -> { - val cardId = globalState?.scanResponse?.card?.cardId + val cardId = globalState.scanResponse?.card?.cardId when (val currency = action.walletData.currency) { is Currency.Blockchain -> { cardId?.let { - currenciesRepository.removeBlockchain( - it, - currency.blockchain - ) + currenciesRepository.removeBlockchain(it, currency.blockchain) } } is Currency.Token -> { @@ -83,11 +84,14 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.FindBlockchainsInUse -> { - val cardFirmware = globalState?.scanResponse?.card?.firmwareVersion + val scanResponse = globalState.scanResponse ?: return + if (scanResponse.isTangemWallet()) return + + val cardFirmware = scanResponse.card.firmwareVersion val blockchains = currenciesRepository.getBlockchains(cardFirmware) .filterNot { walletState?.blockchains?.contains(it) == true } val walletManagers = - action.factory.makeWalletManagersForApp(action.card, blockchains) + tapWalletManager.walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains) scope.launch { walletManagers.map { walletManager -> @@ -120,12 +124,14 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.FindTokensInUse -> { - val card = globalState?.scanResponse?.card ?: return + val scanResponse = globalState.scanResponse ?: return + if (scanResponse.isTangemWallet()) return + + val walletFactory = tapWalletManager.walletManagerFactory + val card = scanResponse.card val walletManager = walletState?.getWalletManager(Blockchain.Ethereum) - ?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( - card = card, - blockchain = Blockchain.Ethereum - ) + ?: walletFactory.makeWalletManagerForApp(scanResponse, Blockchain.Ethereum) + val tokenFinder = walletManager as TokenFinder scope.launch { val result = tokenFinder.findTokens() @@ -159,26 +165,33 @@ class MultiWalletMiddleware { } private fun addToken(token: Token, walletState: WalletState?, globalState: GlobalState?) { - val card = globalState?.scanResponse?.card ?: return + val scanResponse = globalState?.scanResponse ?: return + val walletManager = walletState?.getWalletManager(token) ?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( - card = card, - blockchain = token.blockchain + scanResponse, + token.blockchain )?.also { walletManager -> store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain)) } - store.dispatch( - WalletAction.LoadFiatRate(currency = Currency.Token(token)) - ) + store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Token(token))) scope.launch { when (val result = walletManager?.addToken(token)) { is Result.Success -> { - store.dispatchOnMain( - WalletAction.MultiWallet.TokenLoaded(amount = result.data, token = token) - ) + store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(result.data, token)) + } + is Result.Failure -> { + when (val result = walletManager.safeUpdate()) { + is com.tangem.common.services.Result.Success -> { + val tokenAmount = result.data.getTokenAmount(token) ?: return@launch + store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(tokenAmount, token)) + } + is com.tangem.common.services.Result.Failure -> { + } + } } } } @@ -189,12 +202,13 @@ class MultiWalletMiddleware { walletState: WalletState?, globalState: GlobalState?, ) { - val card = globalState?.scanResponse?.card ?: return + val scanResponse = globalState?.scanResponse ?: return + val tokensWithManagers = tokens.map { token -> val walletManager = walletState?.getWalletManager(token) ?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( - card = card, - blockchain = token.blockchain + scanResponse, + token.blockchain )?.also { walletManager -> store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain)) @@ -208,11 +222,17 @@ class MultiWalletMiddleware { tokensWithManagers.forEach { when (val result = it.walletManager?.addToken(it.token)) { is Result.Success -> { - store.dispatchOnMain( - WalletAction.MultiWallet.TokenLoaded( - amount = result.data, token = it.token - ) - ) + store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(result.data, it.token)) + } + is Result.Failure -> { + when (val result = it.walletManager.safeUpdate()) { + is com.tangem.common.services.Result.Success -> { + val tokenAmount = result.data.getTokenAmount(it.token) ?: return@launch + store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(tokenAmount, it.token)) + } + is com.tangem.common.services.Result.Failure -> { + } + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 6fc58ea550..8eeef0afdc 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -55,7 +55,7 @@ class MultiWalletView : WalletView { private fun setupWalletCardNumber(fragment: WalletFragment) = with(fragment) { val card = store.state.globalState.scanResponse?.card - if (card?.backupStatus?.isActive == true) { + if (card?.backupStatus is Card.BackupStatus.Active) { val cardCount = (card.backupStatus as Card.BackupStatus.Active).cardCount + 1 tv_twin_card_number.show() tv_twin_card_number.text = diff --git a/app/src/main/java/com/tangem/tap/network/Retrofit.kt b/app/src/main/java/com/tangem/tap/network/Retrofit.kt index dd16923173..a2188e6d64 100644 --- a/app/src/main/java/com/tangem/tap/network/Retrofit.kt +++ b/app/src/main/java/com/tangem/tap/network/Retrofit.kt @@ -4,6 +4,7 @@ import com.squareup.moshi.FromJson import com.squareup.moshi.Moshi import com.squareup.moshi.ToJson import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.tangem.common.json.TangemSdkAdapter import com.tangem.wallet.BuildConfig import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -41,7 +42,10 @@ fun createMoshiConverterFactory(): Converter.Factory = MoshiConverterFactory.cre fun createMoshi(): Moshi = Moshi.Builder() .add(BigDecimalAdapter) - .add(KotlinJsonAdapterFactory()).build() + .add(KotlinJsonAdapterFactory()) + .add(TangemSdkAdapter.DerivationPathAdapter()) + .add(TangemSdkAdapter.DerivationNodeAdapter()) + .build() private fun createHttpLoggingInterceptor(): HttpLoggingInterceptor { val logging = HttpLoggingInterceptor() diff --git a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt index a016e233f3..17238689f5 100644 --- a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt @@ -45,7 +45,7 @@ class UsedCardsPrefStorage( } fun activationIsStarted(cardId: String): Boolean { - return findCardInfo(cardId)?.isActivationStarted ?: true + return findCardInfo(cardId)?.isActivationStarted ?: false } fun activationFinished(cardId: String) { diff --git a/app/src/main/res/drawable/shape_chip.xml b/app/src/main/res/drawable/shape_chip.xml index 9544d9916d..658e94c9a7 100644 --- a/app/src/main/res/drawable/shape_chip.xml +++ b/app/src/main/res/drawable/shape_chip.xml @@ -1,7 +1,7 @@ - + + android:fitsSystemWindows="true"> + + + diff --git a/app/src/main/res/layout/fragment_onboarding_wallet.xml b/app/src/main/res/layout/fragment_onboarding_wallet.xml index 00ff82b671..7f004c814d 100644 --- a/app/src/main/res/layout/fragment_onboarding_wallet.xml +++ b/app/src/main/res/layout/fragment_onboarding_wallet.xml @@ -78,30 +78,27 @@ @@ -168,15 +165,14 @@ android:layout_height="wrap_content" android:visibility="gone" app:layout_constraintHorizontal_bias="0.504" - app:layout_constraintBottom_toTopOf="@id/layout_buttons_common" app:flow_verticalBias="0" app:layout_constraintTop_toTopOf="@+id/guideline2" - android:layout_marginBottom="12dp"/> + android:layout_marginBottom="8dp"/> - - - @@ -110,7 +94,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierDirection="start" - app:constraint_referenced_ids="btn_add_token, btn_token_added" /> - + app:constraint_referenced_ids="btn_add_token" /> \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 3bf6353760..93cbbcca62 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -10,7 +10,7 @@ @color/accent #661ACE80 @color/darkGray6 - #661C1C1E + #6A6A6A #5AC461 #C4291C diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 3a6508fd00..e81e6a07bf 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -218,7 +218,7 @@ + Add backup card Scan the card #%d Scan primary card - Buy more cards + Add more cards Backup your wallet No backup cards