Updated on 2026-08-14
This commit is contained in:
parent
5cc1aa7ce8
commit
15f12717f6
22 changed files with 648 additions and 317 deletions
|
|
@ -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() {
|
||||
|
|
|
|||
42
app/src/main/java/com/tangem/tap/di/ActivityModule.kt
Normal file
42
app/src/main/java/com/tangem/tap/di/ActivityModule.kt
Normal file
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ScanResponse> {
|
||||
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<Action>(
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ScanResponse> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ScanResponse> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ScanCardException, ScanResponse> = 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<TangemError, ScanCardException> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +47,7 @@ class UserTokensRepository(
|
|||
storageService.saveUserTokens(userWalletId = userWalletId, tokens = userTokens)
|
||||
}
|
||||
|
||||
// FIXME: Move to data layer
|
||||
suspend fun loadBlockchainsToDerive(card: CardDTO): List<BlockchainNetwork> = 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<Currency> {
|
||||
return DemoHelper.config.demoBlockchains
|
||||
.map { blockchain ->
|
||||
|
|
|
|||
|
|
@ -9,4 +9,6 @@ interface CustomTokenFeatureToggles {
|
|||
|
||||
/** Availability of redesigned screen (internal feature) */
|
||||
val isRedesignedScreenEnabled: Boolean
|
||||
|
||||
val isNewCardScanningEnabled: Boolean
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -217,7 +217,7 @@ internal class WalletSelectorMiddleware {
|
|||
private suspend fun unlockUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult<Unit> {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ object DaggerGraphReducer {
|
|||
return when (action) {
|
||||
is DaggerGraphAction.SetActivityDependencies -> state.daggerGraphState.copy(
|
||||
testerRouter = action.testerRouter,
|
||||
scanCardUseCase = action.scanCardUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <reified T> get(getDependency: DaggerGraphState.() -> T?): T {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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<Chain<ScanCardException.ChainException, ScanResponse>> = emptyArray(),
|
||||
afterScanChains: List<Chain<ScanCardException.ChainException, ScanResponse>> = emptyList(),
|
||||
): Either<ScanCardException, ScanResponse> {
|
||||
resetCardIdDisplayFormat()
|
||||
scanChainProcessor.addChains(afterScanChains)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class ChainProcessor<E, R> {
|
|||
* 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<Chain<E, R>>) {
|
||||
fun addChains(chains: List<Chain<E, R>>) {
|
||||
this.chains.addAll(chains)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue