From 15f12717f6664fe77cb79a1a1ea31afe275ec51b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 May 2023 16:06:36 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 17 +- .../java/com/tangem/tap/di/ActivityModule.kt | 42 +++ .../domain/scanCard/LegacyScanProcessor.kt | 272 +++++++++++++++++ .../tap/domain/scanCard/ScanCardProcessor.kt | 285 +++--------------- .../domain/scanCard/UseCaseScanProcessor.kt | 117 +++++++ .../repository/DefaultScanCardRepository.kt | 38 +++ .../utils/ScanCardExceptionConverter.kt | 47 +++ .../tap/domain/tokens/UserTokensRepository.kt | 2 + .../CustomTokenFeatureToggles.kt | 2 + .../DefaultCustomTokenFeatureToggles.kt | 3 + .../details/redux/DetailsMiddleware.kt | 13 +- .../tap/features/home/redux/HomeMiddleware.kt | 44 ++- .../middlewares/MultiWalletMiddleware.kt | 45 ++- .../redux/WalletSelectorMiddleware.kt | 2 +- .../welcome/redux/WelcomeMiddleware.kt | 7 +- .../tap/proxy/redux/DaggerGraphAction.kt | 6 +- .../tap/proxy/redux/DaggerGraphReducer.kt | 1 + .../tap/proxy/redux/DaggerGraphState.kt | 2 + .../configs/feature_toggles_config.json | 8 +- .../tangem/domain/card/ScanCardException.kt | 6 +- .../com/tangem/domain/card/ScanCardUseCase.kt | 4 +- .../domain/core/chain/ChainProcessor.kt | 2 +- 22 files changed, 648 insertions(+), 317 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/di/ActivityModule.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 7ad61d6a45..ff361d442b 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -10,10 +10,10 @@ import androidx.core.view.WindowInsetsControllerCompat import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.snackbar.Snackbar import com.tangem.TangemSdk +import com.tangem.domain.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter import com.tangem.operations.backup.BackupService import com.tangem.sdk.extensions.init -import com.tangem.sdk.extensions.initWithBiometrics import com.tangem.tap.common.ActivityResultCallbackHolder import com.tangem.tap.common.DialogManager import com.tangem.tap.common.OnActivityResultCallback @@ -77,6 +77,15 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var testerRouter: TesterRouter + @Inject + lateinit var injectedTangemSdk: TangemSdk + + @Inject + lateinit var injectedTangemSdkManager: TangemSdkManager + + @Inject + lateinit var scanCardUseCase: ScanCardUseCase + private var snackbar: Snackbar? = null private val dialogManager = DialogManager() private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind) @@ -89,8 +98,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac systemActions() store.dispatch(NavigationAction.ActivityCreated(WeakReference(this))) - tangemSdk = TangemSdk.initWithBiometrics(this, TangemSdkManager.config) - tangemSdkManager = TangemSdkManager(tangemSdk, this) + tangemSdk = injectedTangemSdk + tangemSdkManager = injectedTangemSdkManager appStateHolder.tangemSdkManager = tangemSdkManager appStateHolder.tangemSdk = tangemSdk backupService = BackupService.init(tangemSdk, this) @@ -104,7 +113,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac ), ) - store.dispatch(DaggerGraphAction.SetActivityDependencies(testerRouter)) + store.dispatch(DaggerGraphAction.SetActivityDependencies(testerRouter, scanCardUseCase)) } private fun initUserWalletsListManager() { diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt new file mode 100644 index 0000000000..96639f64e4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -0,0 +1,42 @@ +package com.tangem.tap.di + +import android.content.Context +import androidx.fragment.app.FragmentActivity +import com.tangem.TangemSdk +import com.tangem.domain.card.ScanCardUseCase +import com.tangem.sdk.extensions.initWithBiometrics +import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository +import com.tangem.tap.userTokensRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.android.qualifiers.ActivityContext +import dagger.hilt.android.scopes.ActivityScoped + +@Module +@InstallIn(ActivityComponent::class) +internal object ActivityModule { + + @Provides + @ActivityScoped + fun provideTangemSdk(@ActivityContext context: Context): TangemSdk { + return TangemSdk.initWithBiometrics(context as FragmentActivity, TangemSdkManager.config) + } + + @Provides + @ActivityScoped + fun provideTangemSdkManager(@ActivityContext context: Context, tangemSdk: TangemSdk): TangemSdkManager { + return TangemSdkManager(tangemSdk, context) + } + + @Provides + @ActivityScoped + fun provideScanCardUseCase(tangemSdk: TangemSdk, tangemSdkManager: TangemSdkManager): ScanCardUseCase { + return ScanCardUseCase( + tangemSdk = tangemSdk, + scanCardRepository = DefaultScanCardRepository(userTokensRepository, tangemSdkManager), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt new file mode 100644 index 0000000000..e5a3579e07 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -0,0 +1,272 @@ +package com.tangem.tap.domain.scanCard + +import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemError +import com.tangem.common.core.TangemSdkError +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.common.services.Result +import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.domain.common.extensions.withMainContext +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.common.util.twinsIsTwinned +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.backup.BackupService +import com.tangem.tap.* +import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor +import com.tangem.tap.common.extensions.addContext +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa +import com.tangem.tap.common.extensions.setContext +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.features.disclaimer.createDisclaimer +import com.tangem.tap.features.disclaimer.redux.DisclaimerAction +import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback +import com.tangem.tap.features.onboarding.OnboardingHelper +import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep +import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory +import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError +import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.rekotlin.Action + +internal object LegacyScanProcessor { + + suspend fun scan( + cardId: String? = null, + allowsRequestAccessCodeFromRepository: Boolean = false, + ): CompletionResult { + return tangemSdkManager.scanProduct( + userTokensRepository, + cardId, + allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository, + ) + } + + @Suppress("LongParameterList") + suspend fun scan( + analyticsEvent: AnalyticsEvent?, + cardId: String?, + onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + onScanStateChange: suspend (scanInProgress: Boolean) -> Unit, + onWalletNotCreated: suspend () -> Unit, + disclaimerWillShow: () -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + onSuccess: suspend (scanResponse: ScanResponse) -> Unit, + ) = withMainContext { + onProgressStateChange(true) + onScanStateChange(true) + + tangemSdkManager.changeDisplayedCardIdNumbersCount(null) + + val result = tangemSdkManager.scanProduct( + userTokensRepository = userTokensRepository, + cardId = cardId, + ) + + store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) + + result + .doOnFailure { error -> + onScanStateChange(false) + onFailure(error) + } + .doOnSuccess { scanResponse -> + tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) + + onScanStateChange(false) + sendAnalytics(analyticsEvent, scanResponse) + + checkForUnfinishedBackupForSaltPay( + backupService = backupService, + scanResponse = scanResponse, + onFailure = onFailure, + nextHandler = { scanResponse1 -> + showDisclaimerIfNeed( + scanResponse = scanResponse1, + disclaimerWillShow = disclaimerWillShow, + onFailure = onFailure, + nextHandler = { scanResponse2 -> + onScanSuccess( + scanResponse = scanResponse2, + onProgressStateChange = onProgressStateChange, + onSuccess = onSuccess, + onWalletNotCreated = onWalletNotCreated, + onFailure = onFailure, + ) + }, + ) + }, + ) + } + } + + private fun sendAnalytics(analyticsEvent: AnalyticsEvent?, scanResponse: ScanResponse) { + analyticsEvent?.let { + // this workaround needed to send CardWasScannedEvent without adding a context + val interceptor = CardContextInterceptor(scanResponse) + val params = it.params.toMutableMap() + interceptor.intercept(params) + it.params = params.toMap() + + Analytics.send(it) + } + } + + /** + * It checks only the SaltPay cards. To check for unfinished backups for the standard Wallet cards + * see BackupAction.CheckForUnfinishedBackup + * If user touches card other than Visa SaltPay - show dialog and block next processing + */ + private suspend inline fun checkForUnfinishedBackupForSaltPay( + backupService: BackupService, + scanResponse: ScanResponse, + nextHandler: (ScanResponse) -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + ) { + if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) { + nextHandler(scanResponse) + return + } + + val isTheSamePrimaryCard = backupService.primaryCardId + ?.let { it == scanResponse.card.cardId } + ?: false + + if (scanResponse.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) { + val error = SaltPayActivationError.PutVisaCard + SaltPayExceptionHandler.handle(error) + onFailure(TangemSdkError.ExceptionError(error)) + } else { + nextHandler(scanResponse) + } + } + + private suspend inline fun showDisclaimerIfNeed( + scanResponse: ScanResponse, + crossinline disclaimerWillShow: () -> Unit = {}, + crossinline nextHandler: suspend (ScanResponse) -> Unit, + crossinline onFailure: suspend (error: TangemError) -> Unit, + ) { + val disclaimer = scanResponse.card.createDisclaimer() + store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer)) + + if (disclaimer.isAccepted()) { + nextHandler(scanResponse) + } else { + scope.launch { + delay(DELAY_SDK_DIALOG_CLOSE) + disclaimerWillShow() + arrayOf( + DisclaimerAction.Show( + fromScreen = AppScreen.Home, + callback = DisclaimerCallback( + onAccept = { + scope.launch(Dispatchers.Main) { + nextHandler(scanResponse) + } + }, + onDismiss = { + scope.launch(Dispatchers.Main) { + onFailure(TangemSdkError.UserCancelled()) + } + }, + ), + ), + ) + } + } + } + + @Suppress("LongMethod", "MagicNumber") + private suspend inline fun onScanSuccess( + scanResponse: ScanResponse, + crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + crossinline onWalletNotCreated: suspend () -> Unit, + crossinline onSuccess: suspend (ScanResponse) -> Unit, + crossinline onFailure: suspend (error: TangemError) -> Unit, + ) { + val globalState = store.state.globalState + val tapWalletManager = globalState.tapWalletManager + tapWalletManager.updateConfigManager(scanResponse) + + store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse)) + + val cardTypesResolver = scanResponse.cardTypesResolver + if (cardTypesResolver.isSaltPay()) { + if (cardTypesResolver.isSaltPayVisa()) { + val manager = SaltPayActivationManagerFactory( + blockchain = scanResponse.cardTypesResolver.getBlockchain(), + card = scanResponse.card, + ).create() + val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager) + delay(500) + withMainContext { + when (result) { + is Result.Success -> { + Analytics.addContext(scanResponse) + val isOnboardingCase = result.data + if (isOnboardingCase) { + onWalletNotCreated() + store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) + store.dispatch(OnboardingSaltPayAction.SetDependencies(manager)) + store.dispatch(OnboardingSaltPayAction.Update(withAnalytics = false)) + navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) } + } else { + delay(DELAY_SDK_DIALOG_CLOSE) + onSuccess(scanResponse) + } + } + is Result.Failure -> { + delay(DELAY_SDK_DIALOG_CLOSE) + SaltPayExceptionHandler.handle(result.error) + onFailure(TangemSdkError.ExceptionError(result.error)) + } + } + } + } else { + delay(DELAY_SDK_DIALOG_CLOSE) + if (scanResponse.card.backupStatus?.isActive == false) { + val error = SaltPayActivationError.PutVisaCard + SaltPayExceptionHandler.handle(error) + onFailure(TangemSdkError.ExceptionError(error)) + } else { + Analytics.setContext(scanResponse) + onSuccess(scanResponse) + } + } + } else { + if (OnboardingHelper.isOnboardingCase(scanResponse)) { + Analytics.addContext(scanResponse) + onWalletNotCreated() + store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) + val appScreen = OnboardingHelper.whereToNavigate(scanResponse) + navigateTo(appScreen) { onProgressStateChange(it) } + } else { + Analytics.setContext(scanResponse) + if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { + onWalletNotCreated() + store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) + navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) } + } else { + delay(DELAY_SDK_DIALOG_CLOSE) + onSuccess(scanResponse) + } + } + } + } + + private suspend inline fun navigateTo(screen: AppScreen, onProgressStateChange: (showProgress: Boolean) -> Unit) { + delay(DELAY_SDK_DIALOG_CLOSE) + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) + onProgressStateChange(false) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index 86f790ee67..0546a79db5 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -1,49 +1,31 @@ package com.tangem.tap.domain.scanCard +import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError -import com.tangem.common.core.TangemSdkError -import com.tangem.common.doOnFailure -import com.tangem.common.doOnSuccess -import com.tangem.common.services.Result -import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.AnalyticsEvent -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.ScanResponse -import com.tangem.operations.backup.BackupService -import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.tap.backupService -import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor -import com.tangem.tap.common.extensions.addContext -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa -import com.tangem.tap.common.extensions.setContext -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.features.disclaimer.createDisclaimer -import com.tangem.tap.features.disclaimer.redux.DisclaimerAction -import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback -import com.tangem.tap.features.onboarding.OnboardingHelper -import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper -import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction -import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction -import com.tangem.tap.preferencesStorage -import com.tangem.tap.scope +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userTokensRepository -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -// TODO: Create repository for that -object ScanCardProcessor { +// TODO: Remove this object after feature toggle was removed and use ScanCardUseCase instead +internal object ScanCardProcessor { + private val isNewCardScanningEnabled: Boolean + get() = store.state.daggerGraphState + .get(DaggerGraphState::customTokenFeatureToggles) + .isNewCardScanningEnabled + + suspend fun scan( + cardId: String? = null, + allowsRequestAccessCodeFromRepository: Boolean = false, + ): CompletionResult { + return if (isNewCardScanningEnabled) { + UseCaseScanProcessor.scan(cardId, allowsRequestAccessCodeFromRepository) + } else { + LegacyScanProcessor.scan(cardId, allowsRequestAccessCodeFromRepository) + } + } + + @Suppress("LongParameterList") suspend fun scan( analyticsEvent: AnalyticsEvent? = null, cardId: String? = null, @@ -53,212 +35,29 @@ object ScanCardProcessor { disclaimerWillShow: () -> Unit = {}, onFailure: suspend (error: TangemError) -> Unit = {}, onSuccess: suspend (scanResponse: ScanResponse) -> Unit = {}, - ) = withMainContext { - onProgressStateChange(true) - onScanStateChange(true) - - tangemSdkManager.changeDisplayedCardIdNumbersCount(null) - - val result = tangemSdkManager.scanProduct( - userTokensRepository = userTokensRepository, - cardId = cardId, - ) - - store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) - - result - .doOnFailure { error -> - onScanStateChange(false) - onFailure(error) - } - .doOnSuccess { scanResponse -> - tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) - - onScanStateChange(false) - sendAnalytics(analyticsEvent, scanResponse) - - checkForUnfinishedBackupForSaltPay( - backupService = backupService, - scanResponse = scanResponse, - onFailure = onFailure, - nextHandler = { scanResponse1 -> - showDisclaimerIfNeed( - scanResponse = scanResponse1, - disclaimerWillShow = disclaimerWillShow, - onFailure = onFailure, - nextHandler = { scanResponse2 -> - onScanSuccess( - scanResponse = scanResponse2, - onProgressStateChange = onProgressStateChange, - onSuccess = onSuccess, - onWalletNotCreated = onWalletNotCreated, - onFailure = onFailure, - ) - }, - ) - }, - ) - } - } - - private fun sendAnalytics(analyticsEvent: AnalyticsEvent?, scanResponse: ScanResponse) { - analyticsEvent?.let { - // this workaround needed to send CardWasScannedEvent without adding a context - val interceptor = CardContextInterceptor(scanResponse) - val params = it.params.toMutableMap() - interceptor.intercept(params) - it.params = params.toMap() - - Analytics.send(it) - } - } - - /** - * It checks only the SaltPay cards. To check for unfinished backups for the standard Wallet cards - * see BackupAction.CheckForUnfinishedBackup - * If user touches card other than Visa SaltPay - show dialog and block next processing - */ - private suspend inline fun checkForUnfinishedBackupForSaltPay( - backupService: BackupService, - scanResponse: ScanResponse, - nextHandler: (ScanResponse) -> Unit, - onFailure: suspend (error: TangemError) -> Unit, ) { - if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) { - nextHandler(scanResponse) - return - } - - val isTheSamePrimaryCard = backupService.primaryCardId - ?.let { it == scanResponse.card.cardId } - ?: false - - if (scanResponse.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) { - val error = SaltPayActivationError.PutVisaCard - SaltPayExceptionHandler.handle(error) - onFailure(TangemSdkError.ExceptionError(error)) + if (isNewCardScanningEnabled) { + UseCaseScanProcessor.scan( + analyticsEvent, + cardId, + onProgressStateChange, + onScanStateChange, + onWalletNotCreated, + disclaimerWillShow, + onFailure, + onSuccess, + ) } else { - nextHandler(scanResponse) + LegacyScanProcessor.scan( + analyticsEvent, + cardId, + onProgressStateChange, + onScanStateChange, + onWalletNotCreated, + disclaimerWillShow, + onFailure, + onSuccess, + ) } } - - private suspend inline fun showDisclaimerIfNeed( - scanResponse: ScanResponse, - crossinline disclaimerWillShow: () -> Unit = {}, - crossinline nextHandler: suspend (ScanResponse) -> Unit, - crossinline onFailure: suspend (error: TangemError) -> Unit, - ) { - val disclaimer = scanResponse.card.createDisclaimer() - store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer)) - - if (disclaimer.isAccepted()) { - nextHandler(scanResponse) - } else { - scope.launch { - delay(DELAY_SDK_DIALOG_CLOSE) - disclaimerWillShow() - dispatchOnMain( - DisclaimerAction.Show( - fromScreen = AppScreen.Home, - callback = DisclaimerCallback( - onAccept = { - scope.launch(Dispatchers.Main) { - nextHandler(scanResponse) - } - }, - onDismiss = { - scope.launch(Dispatchers.Main) { - onFailure(TangemSdkError.UserCancelled()) - } - }, - ), - ), - ) - } - } - } - - @Suppress("LongMethod", "MagicNumber") - private suspend inline fun onScanSuccess( - scanResponse: ScanResponse, - crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, - crossinline onWalletNotCreated: suspend () -> Unit, - crossinline onSuccess: suspend (ScanResponse) -> Unit, - crossinline onFailure: suspend (error: TangemError) -> Unit, - ) { - val globalState = store.state.globalState - val tapWalletManager = globalState.tapWalletManager - tapWalletManager.updateConfigManager(scanResponse) - - store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse)) - - val cardTypesResolver = scanResponse.cardTypesResolver - if (cardTypesResolver.isSaltPay()) { - if (cardTypesResolver.isSaltPayVisa()) { - val manager = SaltPayActivationManagerFactory( - blockchain = scanResponse.cardTypesResolver.getBlockchain(), - card = scanResponse.card, - ).create() - val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager) - delay(500) - withMainContext { - when (result) { - is Result.Success -> { - Analytics.addContext(scanResponse) - val isOnboardingCase = result.data - if (isOnboardingCase) { - onWalletNotCreated() - store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) - store.dispatch(OnboardingSaltPayAction.SetDependencies(manager)) - store.dispatch(OnboardingSaltPayAction.Update(withAnalytics = false)) - navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) } - } else { - delay(DELAY_SDK_DIALOG_CLOSE) - onSuccess(scanResponse) - } - } - is Result.Failure -> { - delay(DELAY_SDK_DIALOG_CLOSE) - SaltPayExceptionHandler.handle(result.error) - onFailure(TangemSdkError.ExceptionError(result.error)) - } - } - } - } else { - delay(DELAY_SDK_DIALOG_CLOSE) - if (scanResponse.card.backupStatus?.isActive == false) { - val error = SaltPayActivationError.PutVisaCard - SaltPayExceptionHandler.handle(error) - onFailure(TangemSdkError.ExceptionError(error)) - } else { - Analytics.setContext(scanResponse) - onSuccess(scanResponse) - } - } - } else { - if (OnboardingHelper.isOnboardingCase(scanResponse)) { - Analytics.addContext(scanResponse) - onWalletNotCreated() - store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) - val appScreen = OnboardingHelper.whereToNavigate(scanResponse) - navigateTo(appScreen) { onProgressStateChange(it) } - } else { - Analytics.setContext(scanResponse) - if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { - onWalletNotCreated() - store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) - navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) } - } else { - delay(DELAY_SDK_DIALOG_CLOSE) - onSuccess(scanResponse) - } - } - } - } - - private suspend inline fun navigateTo(screen: AppScreen, onProgressStateChange: (showProgress: Boolean) -> Unit) { - delay(DELAY_SDK_DIALOG_CLOSE) - store.dispatchOnMain(NavigationAction.NavigateTo(screen)) - onProgressStateChange(false) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt new file mode 100644 index 0000000000..52d6cf4339 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt @@ -0,0 +1,117 @@ +package com.tangem.tap.domain.scanCard + +import arrow.fx.coroutines.resourceScope +import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemError +import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.domain.card.ScanCardException +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE +import com.tangem.tap.backupService +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.scanCard.chains.* +import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter +import com.tangem.tap.preferencesStorage +import com.tangem.tap.proxy.redux.DaggerGraphState +import com.tangem.tap.store +import kotlinx.coroutines.delay + +internal object UseCaseScanProcessor { + private val scanCardExceptionConverter = ScanCardExceptionConverter() + + suspend fun scan( + cardId: String? = null, + allowsRequestAccessCodeFromRepository: Boolean = false, + ): CompletionResult { + val scanCardUseCase = store.state.daggerGraphState.get(DaggerGraphState::scanCardUseCase) + return scanCardUseCase(cardId, allowsRequestAccessCodeFromRepository) + .fold( + ifLeft = { CompletionResult.Failure(scanCardExceptionConverter.convertBack(it)) }, + ifRight = { CompletionResult.Success(it) }, + ) + } + + @Suppress("LongParameterList") + suspend fun scan( + analyticsEvent: AnalyticsEvent?, + cardId: String?, + onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + onScanStateChange: suspend (scanInProgress: Boolean) -> Unit, + onWalletNotCreated: suspend () -> Unit, + disclaimerWillShow: () -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + onSuccess: suspend (scanResponse: ScanResponse) -> Unit, + ) = progressScope(onProgressStateChange) { + onScanStateChange(true) + + val scanCardUseCase = store.state.daggerGraphState.get(DaggerGraphState::scanCardUseCase) + val chains = buildList { + add(ScanningFinishedChain { onScanStateChange(false) }) + if (analyticsEvent != null) { + add(AnalyticsChain(analyticsEvent)) + } + add(CheckForUnfinishedSaltPayBackupChain(backupService)) + add(DisclaimerChain(store, disclaimerWillShow)) + add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager, preferencesStorage)) + } + + scanCardUseCase(cardId, afterScanChains = chains) + .map { onSuccess(it) } + .mapLeft { proceedWithException(it, onWalletNotCreated, onFailure) } + } + + private suspend fun proceedWithException( + exception: ScanCardException, + onWalletNotCreated: suspend () -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + ) { + when (exception) { + is ScanCardException.ChainException -> proceedWithScanChainException( + exception, + onWalletNotCreated, + onFailure, + ) + is ScanCardException.UnknownException, + is ScanCardException.UserCancelled, + is ScanCardException.WrongAccessCode, + is ScanCardException.WrongCardId, + -> onFailure(scanCardExceptionConverter.convertBack(exception)) + } + } + + private suspend fun proceedWithScanChainException( + exception: ScanCardException.ChainException, + onWalletNotCreated: suspend () -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + ) { + when (exception) { + is ScanChainException.OnboardingNeeded -> { + navigateTo(exception.onboardingRoute) + onWalletNotCreated() + } + is ScanChainException.PutSaltPayVisaCard, + is ScanChainException.DisclaimerWasCanceled, + is ScanChainException.CheckForSaltPayOnboardingCaseException, + -> onFailure(scanCardExceptionConverter.convertBack(exception)) + } + } + + private suspend fun progressScope( + onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + action: suspend () -> Unit, + ) = resourceScope { + install( + acquire = { onProgressStateChange(true) }, + release = { _, _ -> onProgressStateChange(false) }, + ) + + action() + } + + private suspend inline fun navigateTo(screen: AppScreen) { + delay(DELAY_SDK_DIALOG_CLOSE) + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt new file mode 100644 index 0000000000..2de676ba6c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt @@ -0,0 +1,38 @@ +package com.tangem.tap.domain.scanCard.repository + +import arrow.core.Either +import arrow.core.raise.either +import com.tangem.common.CompletionResult +import com.tangem.domain.card.ScanCardException +import com.tangem.domain.card.repository.ScanCardRepository +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter +import com.tangem.tap.domain.tokens.UserTokensRepository + +internal class DefaultScanCardRepository( + // FIXME: The repository should not depend on another repository. + // But now we need to provide loadBlockchainsToDerive() to ScanProductTask and it's hard to move this method from + // UserTokensRepository. + private val userTokensRepository: UserTokensRepository, + private val tangemSdkManager: TangemSdkManager, +) : ScanCardRepository { + + private val exceptionConverter = ScanCardExceptionConverter() + + override suspend fun scanCard( + cardId: String?, + allowRequestAccessCodeFromStorage: Boolean, + ): Either = either { + when ( + val result = tangemSdkManager.scanProduct( + cardId = cardId, + userTokensRepository = userTokensRepository, + allowsRequestAccessCodeFromRepository = allowRequestAccessCodeFromStorage, + ) + ) { + is CompletionResult.Success -> result.data + is CompletionResult.Failure -> raise(exceptionConverter.convert(result.error)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt new file mode 100644 index 0000000000..f04df13017 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt @@ -0,0 +1,47 @@ +package com.tangem.tap.domain.scanCard.utils + +import com.tangem.common.core.TangemError +import com.tangem.common.core.TangemSdkError +import com.tangem.domain.card.ScanCardException +import com.tangem.tap.domain.scanCard.chains.ScanChainException +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError +import com.tangem.utils.converter.TwoWayConverter + +internal class ScanCardExceptionConverter : TwoWayConverter { + override fun convert(value: TangemError): ScanCardException = when (value) { + is TangemSdkError -> convertTangemSdkError(value) + else -> ScanCardException.UnknownException(value) + } + + override fun convertBack(value: ScanCardException): TangemError { + return when (value) { + is ScanCardException.UnknownException -> TangemSdkError.ExceptionError(value) + is ScanCardException.UserCancelled -> TangemSdkError.UserCancelled() + is ScanCardException.WrongAccessCode -> TangemSdkError.WrongAccessCode() + is ScanCardException.WrongCardId -> TangemSdkError.WrongCardNumber(value.cardId) + is ScanCardException.ChainException -> concertScanChainException(value) + } + } + + private fun convertTangemSdkError(value: TangemSdkError): ScanCardException = when (value) { + is TangemSdkError.UserCancelled -> ScanCardException.UserCancelled + is TangemSdkError.WrongAccessCode, + is TangemSdkError.WrongPasscode, + -> ScanCardException.WrongAccessCode + is TangemSdkError.WrongCardNumber -> ScanCardException.WrongCardId(value.cardId) + else -> ScanCardException.UnknownException(value) + } + + private fun concertScanChainException(value: ScanCardException.ChainException): TangemError { + return when (val e = value as? ScanChainException) { + is ScanChainException.DisclaimerWasCanceled -> TangemSdkError.UserCancelled() + is ScanChainException.PutSaltPayVisaCard -> TangemSdkError.ExceptionError( + SaltPayActivationError.PutVisaCard, + ) + is ScanChainException.CheckForSaltPayOnboardingCaseException, + is ScanChainException.OnboardingNeeded, + null, + -> TangemSdkError.ExceptionError(e?.cause) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 255d8611c5..bb1c27c80e 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -47,6 +47,7 @@ class UserTokensRepository( storageService.saveUserTokens(userWalletId = userWalletId, tokens = userTokens) } + // FIXME: Move to data layer suspend fun loadBlockchainsToDerive(card: CardDTO): List = withContext(dispatchers.io) { val userWalletId = getUserWalletId(card) ?: return@withContext emptyList() val blockchainNetworks = loadTokensOffline(userWalletId = userWalletId).toBlockchainNetworks() @@ -62,6 +63,7 @@ class UserTokensRepository( return storageService.getUserTokens(userWalletId = userWalletId) ?: emptyList() } + // FIXME: Move to user wallet config private fun loadDemoCurrencies(): List { return DemoHelper.config.demoBlockchains .map { blockchain -> diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt index 6de26f4bd2..b64f9efd14 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt @@ -9,4 +9,6 @@ interface CustomTokenFeatureToggles { /** Availability of redesigned screen (internal feature) */ val isRedesignedScreenEnabled: Boolean + + val isNewCardScanningEnabled: Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt index fbdc41b2c0..a19dde5403 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt @@ -16,4 +16,7 @@ internal class DefaultCustomTokenFeatureToggles( override val isRedesignedScreenEnabled: Boolean get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_CUSTOM_TOKEN_SCREEN_ENABLED") + + override val isNewCardScanningEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled(name = "NEW_CARD_SCANNING_ENABLED") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index b10ad9de2a..acc0130ea9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -24,6 +24,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.model.builders.UserWalletIdBuilder +import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation @@ -32,6 +33,13 @@ import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.foregroundActivityObserver +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userWalletsListManager +import com.tangem.tap.walletStoresManager import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -72,10 +80,7 @@ class DetailsMiddleware { is DetailsAction.AccessCodeRecovery -> accessCodeRecoveryMiddleware.handle(state, action) DetailsAction.ScanCard -> { scope.launch { - tangemSdkManager.scanProduct( - userTokensRepository = userTokensRepository, - allowsRequestAccessCodeFromRepository = true, - ) + ScanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true) .doOnSuccess { scanResponse -> // if we use biometric, scanResponse in GlobalState is null, and crashes NPE on twin cards store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) 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 f534813ecb..9d7d775e1d 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 @@ -3,9 +3,11 @@ package com.tangem.tap.features.home.redux import com.tangem.common.doOnFailure import com.tangem.common.doOnResult import com.tangem.common.doOnSuccess +import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.AnalyticsEvent import com.tangem.domain.models.scan.ScanResponse +import com.tangem.tap.* import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.analytics.events.Shop @@ -25,11 +27,6 @@ import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.home.redux.HomeMiddleware.BUY_WALLET_URL import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.signin.redux.SignInAction -import com.tangem.tap.preferencesStorage -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action @@ -80,6 +77,7 @@ private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, ) + ScanCardProcessor.scan( analyticsEvent = analyticsEvent, onProgressStateChange = { showProgress -> @@ -93,6 +91,7 @@ private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { store.dispatch(HomeAction.ScanInProgress(scanInProgress)) }, onFailure = { + Timber.e(it, "Unable to scan card") changeButtonState(ButtonState.ENABLED) }, onSuccess = { scanResponse -> @@ -101,26 +100,23 @@ private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { ) } -fun proceedWithScanResponse(scanResponse: ScanResponse) { - scope.launch { - val userWallet = UserWalletBuilder(scanResponse).build() - if (userWallet == null) { - Timber.e("User wallet not created") - return@launch - } - - userWalletsListManager.save(userWallet) - .doOnFailure { error -> - Timber.e(error, "Unable to save user wallet") - } - .doOnSuccess { - scope.launch { store.onUserWalletSelected(userWallet = userWallet) } - } - .doOnResult { - store.dispatchOnMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Card)) - navigateTo(AppScreen.Wallet) - } +private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch { + val userWallet = UserWalletBuilder(scanResponse).build().guard { + Timber.e("User wallet not created") + return@launch } + + userWalletsListManager.save(userWallet) + .doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + } + .doOnSuccess { + scope.launch { store.onUserWalletSelected(userWallet = userWallet) } + } + .doOnResult { + store.dispatchOnMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Card)) + navigateTo(AppScreen.Wallet) + } } private suspend fun navigateTo(appScreen: AppScreen) { 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 d7c7bd6d85..607d8c63c4 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 @@ -15,13 +15,12 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.scope import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userTokensRepository import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletCurrenciesManager import kotlinx.coroutines.Dispatchers @@ -96,33 +95,27 @@ class MultiWalletMiddleware { return } store.state.globalState.topUpController?.scanToGetDerivations() - scanAndUpdateCard(selectedUserWallet, walletState) + scanAndUpdateCard(selectedUserWallet) } else -> {} } } - private fun scanAndUpdateCard(selectedUserWallet: UserWallet, state: WalletState?) = - scope.launch(Dispatchers.Default) { - tangemSdkManager.scanProduct( - cardId = selectedUserWallet.cardId, - userTokensRepository = userTokensRepository, - additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain }, - allowsRequestAccessCodeFromRepository = true, - ) - .flatMap { scanResponse -> - userWalletsListManager.update( - userWalletId = selectedUserWallet.walletId, - update = { userWallet -> - userWallet.copy( - scanResponse = scanResponse, - ) - }, - ) - } - .doOnSuccess { updatedUserWallet -> - store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList())) - store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) - } - } + private fun scanAndUpdateCard(selectedUserWallet: UserWallet) = scope.launch(Dispatchers.Default) { + ScanCardProcessor.scan(selectedUserWallet.cardId, allowsRequestAccessCodeFromRepository = true) + .flatMap { scanResponse -> + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy( + scanResponse = scanResponse, + ) + }, + ) + } + .doOnSuccess { updatedUserWallet -> + store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList())) + store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index 59336d8989..077c90ca2c 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -217,7 +217,7 @@ internal class WalletSelectorMiddleware { private suspend fun unlockUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { Analytics.send(MyWallets.Button.WalletUnlockTapped()) tangemSdkManager.changeDisplayedCardIdNumbersCount(userWallet.scanResponse) - return tangemSdkManager.scanProduct(userTokensRepository) + return ScanCardProcessor.scan() .map { scanResponse -> val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build() if (scannedUserWalletId == userWallet.walletId) { diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 6bd65c13eb..a7dc158068 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -8,6 +8,7 @@ import com.tangem.common.doOnSuccess import com.tangem.common.flatMap import com.tangem.common.map import com.tangem.domain.models.scan.ScanResponse +import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.extensions.dispatchOnMain @@ -21,12 +22,6 @@ import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.domain.userWalletList.unlockIfLockable import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.tap.features.signin.redux.SignInAction -import com.tangem.tap.intentHandler -import com.tangem.tap.preferencesStorage -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.launch import org.rekotlin.Middleware import timber.log.Timber diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt index 2952ed0257..3c6621663b 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt @@ -1,9 +1,13 @@ package com.tangem.tap.proxy.redux +import com.tangem.domain.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter import org.rekotlin.Action sealed interface DaggerGraphAction : Action { - data class SetActivityDependencies(val testerRouter: TesterRouter) : DaggerGraphAction + data class SetActivityDependencies( + val testerRouter: TesterRouter, + val scanCardUseCase: ScanCardUseCase, + ) : DaggerGraphAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt index 43cb94af5c..267665cce3 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt @@ -14,6 +14,7 @@ object DaggerGraphReducer { return when (action) { is DaggerGraphAction.SetActivityDependencies -> state.daggerGraphState.copy( testerRouter = action.testerRouter, + scanCardUseCase = action.scanCardUseCase, ) } } diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 73fc659cf8..3ffa120662 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -2,6 +2,7 @@ package com.tangem.tap.proxy.redux import com.tangem.datasource.asset.AssetReader import com.tangem.datasource.connection.NetworkConnectionManager +import com.tangem.domain.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles import org.rekotlin.StateType @@ -11,6 +12,7 @@ data class DaggerGraphState( val testerRouter: TesterRouter? = null, val networkConnectionManager: NetworkConnectionManager? = null, val customTokenFeatureToggles: CustomTokenFeatureToggles? = null, + val scanCardUseCase: ScanCardUseCase? = null, ) : StateType { inline fun get(getDependency: DaggerGraphState.() -> T?): T { diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index c44daa2e41..d2cc7402fd 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -5,6 +5,10 @@ }, { "name": "REDESIGNED_CUSTOM_TOKEN_SCREEN_ENABLED", - "version": "4.6.0" + "version": "4.7.0" + }, + { + "name": "NEW_CARD_SCANNING_ENABLED", + "version": "4.7.0" } -] \ No newline at end of file +] diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardException.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardException.kt index b9d3e287ac..3f153ee689 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardException.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardException.kt @@ -2,13 +2,13 @@ package com.tangem.domain.card // TODO: May be add new error types sealed class ScanCardException : Exception() { - object WrongCardId : ScanCardException() - object UserCancelled : ScanCardException() object WrongAccessCode : ScanCardException() open class ChainException : ScanCardException() - class UnknownException(override val cause: Exception) : ScanCardException() + data class UnknownException(override val cause: Exception) : ScanCardException() + + data class WrongCardId(val cardId: String) : ScanCardException() } \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt index 0365ac58ae..b6bab97249 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt @@ -35,14 +35,14 @@ class ScanCardUseCase( * Defaults to null. * @param allowRequestAccessCodeFromStorage whether to prompt the user for an access code if needed. * Defaults to false. - * @param afterScanChains An array of chains that should be executed after a successful card scan operation. + * @param afterScanChains A list of chains that should be executed after a successful card scan operation. * Defaults to an empty array. * @return A [EitherNel] object with either a non-empty list of [ScanCardException] or a [ScanResponse]. */ suspend operator fun invoke( cardId: String? = null, allowRequestAccessCodeFromStorage: Boolean = false, - afterScanChains: Array> = emptyArray(), + afterScanChains: List> = emptyList(), ): Either { resetCardIdDisplayFormat() scanChainProcessor.addChains(afterScanChains) diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/chain/ChainProcessor.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/chain/ChainProcessor.kt index 13101be7fd..c1cac09eb3 100644 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/chain/ChainProcessor.kt +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/chain/ChainProcessor.kt @@ -21,7 +21,7 @@ class ChainProcessor { * Adds chains to the existing list of chains to be executed. * @param chains the chains to be added to the list */ - fun addChains(chains: Array>) { + fun addChains(chains: List>) { this.chains.addAll(chains) }