Updated on 2026-08-14

This commit is contained in:
Tangem 2024-03-19 18:11:56 +00:00
commit b76e1bbc45
219 changed files with 4585 additions and 5271 deletions

View file

@ -47,6 +47,11 @@ class TapWalletManager(
config = blockchainSdkConfig,
accountCreator = store.inject(DaggerGraphState::accountCreator),
blockchainDataStorage = store.inject(DaggerGraphState::blockchainDataStorage),
loggers = if (store.inject(DaggerGraphState::feedbackManagerFeatureToggles).isLocalLogsEnabled) {
listOf(store.inject(DaggerGraphState::blockchainSDKLogger))
} else {
emptyList()
},
)
}

View file

@ -30,7 +30,6 @@ internal class DefaultScanCardProcessor : ScanCardProcessor {
analyticsEvent: AnalyticsEvent?,
cardId: String?,
onProgressStateChange: suspend (showProgress: Boolean) -> Unit,
onScanStateChange: suspend (scanInProgress: Boolean) -> Unit,
onWalletNotCreated: suspend () -> Unit,
disclaimerWillShow: () -> Unit,
onFailure: suspend (error: TangemError) -> Unit,
@ -41,7 +40,6 @@ internal class DefaultScanCardProcessor : ScanCardProcessor {
analyticsEvent,
cardId,
onProgressStateChange,
onScanStateChange,
onWalletNotCreated,
disclaimerWillShow,
onFailure,
@ -52,7 +50,6 @@ internal class DefaultScanCardProcessor : ScanCardProcessor {
analyticsEvent,
cardId,
onProgressStateChange,
onScanStateChange,
onWalletNotCreated,
disclaimerWillShow,
onFailure,

View file

@ -48,14 +48,12 @@ internal object LegacyScanProcessor {
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)
@ -65,13 +63,11 @@ internal object LegacyScanProcessor {
result
.doOnFailure { error ->
onScanStateChange(false)
onFailure(error)
}
.doOnSuccess { scanResponse ->
tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
onScanStateChange(false)
sendAnalytics(analyticsEvent, scanResponse)
showDisclaimerIfNeed(

View file

@ -3,11 +3,15 @@ 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.Analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.StateDialog
import com.tangem.domain.card.ScanCardException
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.domain.scanCard.chains.*
@ -25,9 +29,15 @@ internal object UseCaseScanProcessor {
allowsRequestAccessCodeFromRepository: Boolean = false,
): CompletionResult<ScanResponse> {
val scanCardUseCase = store.inject(DaggerGraphState::scanCardUseCase)
return scanCardUseCase(cardId, allowsRequestAccessCodeFromRepository)
.fold(
ifLeft = { CompletionResult.Failure(scanCardExceptionConverter.convertBack(it)) },
ifLeft = {
val error = scanCardExceptionConverter.convertBack(it)
Analytics.send(Basic.ScanError(error))
CompletionResult.Failure(error)
},
ifRight = { CompletionResult.Success(it) },
)
}
@ -37,17 +47,14 @@ internal object UseCaseScanProcessor {
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.inject(DaggerGraphState::scanCardUseCase)
val chains = buildList {
add(ScanningFinishedChain { onScanStateChange(false) })
add(FailedScansCounterChain(UseCaseScanProcessor::showMaxUnsuccessfulScansReachedDialog))
if (analyticsEvent != null) {
add(AnalyticsChain(analyticsEvent))
}
@ -55,9 +62,14 @@ internal object UseCaseScanProcessor {
add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager))
}
scanCardUseCase(cardId, afterScanChains = chains)
.map { onSuccess(it) }
.mapLeft { proceedWithException(it, onWalletNotCreated, onFailure) }
scanCardUseCase(cardId, afterScanChains = chains).fold(
ifLeft = { proceedWithException(it, onWalletNotCreated, onFailure) },
ifRight = { onSuccess(it) },
)
}
private fun showMaxUnsuccessfulScansReachedDialog() {
store.dispatchDialogShow(StateDialog.ScanFailsDialog)
}
private suspend fun proceedWithException(
@ -75,7 +87,12 @@ internal object UseCaseScanProcessor {
is ScanCardException.UserCancelled,
is ScanCardException.WrongAccessCode,
is ScanCardException.WrongCardId,
-> onFailure(scanCardExceptionConverter.convertBack(exception))
-> {
val error = scanCardExceptionConverter.convertBack(exception)
Analytics.send(Basic.ScanError(error))
onFailure(error)
}
}
}
@ -89,8 +106,9 @@ internal object UseCaseScanProcessor {
navigateTo(exception.onboardingRoute)
onWalletNotCreated()
}
is ScanChainException.DisclaimerWasCanceled,
-> onFailure(scanCardExceptionConverter.convertBack(exception))
is ScanChainException.DisclaimerWasCanceled -> {
onFailure(scanCardExceptionConverter.convertBack(exception))
}
}
}

View file

@ -1,11 +1,11 @@
package com.tangem.tap.domain.scanCard.chains
import arrow.core.Either
import arrow.core.right
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.domain.card.ScanCardException
import com.tangem.domain.core.chain.Chain
import com.tangem.domain.core.chain.ResultChain
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor
@ -19,11 +19,9 @@ import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor
*/
class AnalyticsChain(
private val event: AnalyticsEvent,
) : Chain<ScanCardException.ChainException, ScanResponse> {
) : ResultChain<ScanCardException, ScanResponse>() {
override suspend fun invoke(
previousChainResult: ScanResponse,
): Either<ScanCardException.ChainException, ScanResponse> {
override suspend fun launch(previousChainResult: ScanResponse): ScanChainResult {
val interceptor = CardContextInterceptor(previousChainResult)
val params = event.params.toMutableMap()
interceptor.intercept(params)

View file

@ -1,6 +1,5 @@
package com.tangem.tap.domain.scanCard.chains
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.core.analytics.Analytics
@ -9,6 +8,7 @@ import com.tangem.domain.card.ScanCardException
import com.tangem.domain.common.TapWorkarounds.canSkipBackup
import com.tangem.domain.common.util.twinsIsTwinned
import com.tangem.domain.core.chain.Chain
import com.tangem.domain.core.chain.ResultChain
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.extensions.addContext
import com.tangem.tap.common.extensions.dispatchOnMain
@ -39,11 +39,9 @@ import org.rekotlin.Store
class CheckForOnboardingChain(
private val store: Store<AppState>,
private val tapWalletManager: TapWalletManager,
) : Chain<ScanCardException.ChainException, ScanResponse> {
) : ResultChain<ScanCardException, ScanResponse>() {
override suspend fun invoke(
previousChainResult: ScanResponse,
): Either<ScanCardException.ChainException, ScanResponse> {
override suspend fun launch(previousChainResult: ScanResponse): ScanChainResult {
tapWalletManager.updateConfigManager(previousChainResult)
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(previousChainResult))

View file

@ -1,11 +1,11 @@
package com.tangem.tap.domain.scanCard.chains
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.core.navigation.AppScreen
import com.tangem.domain.card.ScanCardException
import com.tangem.domain.core.chain.Chain
import com.tangem.domain.core.chain.ResultChain
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
@ -31,11 +31,9 @@ import kotlin.coroutines.resume
internal class DisclaimerChain(
private val store: Store<AppState>,
private val disclaimerWillShow: () -> Unit = {},
) : Chain<ScanCardException.ChainException, ScanResponse> {
) : ResultChain<ScanCardException, ScanResponse>() {
override suspend fun invoke(
previousChainResult: ScanResponse,
): Either<ScanCardException.ChainException, ScanResponse> {
override suspend fun launch(previousChainResult: ScanResponse): ScanChainResult {
val disclaimer = previousChainResult.card.createDisclaimer()
return if (disclaimer.isAccepted()) {
@ -46,10 +44,7 @@ internal class DisclaimerChain(
}
}
private suspend fun showDisclaimer(
disclaimer: Disclaimer,
response: ScanResponse,
): Either<ScanCardException.ChainException, ScanResponse> {
private suspend fun showDisclaimer(disclaimer: Disclaimer, response: ScanResponse): ScanChainResult {
store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer))
return suspendCancellableCoroutine { continuation ->

View file

@ -0,0 +1,29 @@
package com.tangem.tap.domain.scanCard.chains
import com.tangem.domain.card.ScanCardException
import com.tangem.domain.core.chain.Chain
import com.tangem.domain.models.scan.ScanResponse
internal class FailedScansCounterChain(
private val onMaxUnsuccessfulScansReached: () -> Unit,
private val maxUnsuccessfulScans: Int = 3,
) : Chain<ScanCardException, ScanResponse> {
override suspend fun launch(previousChainResult: ScanChainResult): ScanChainResult {
if (previousChainResult.isLeft()) {
unsuccessfulScansCounter = unsuccessfulScansCounter.inc().coerceAtMost(maxUnsuccessfulScans)
if (unsuccessfulScansCounter == maxUnsuccessfulScans) {
onMaxUnsuccessfulScansReached()
}
} else {
unsuccessfulScansCounter = 0
}
return previousChainResult
}
private companion object {
var unsuccessfulScansCounter = 0
}
}

View file

@ -8,7 +8,11 @@ sealed class ScanChainException : ScanCardException.ChainException() {
/**
* May be returned from [DisclaimerChain]
* */
object DisclaimerWasCanceled : ScanChainException()
data object DisclaimerWasCanceled : ScanChainException() {
@Suppress("UnusedPrivateMember")
private fun readResolve(): Any = DisclaimerWasCanceled
}
/**
* May be returned from [CheckForOnboardingChain]

View file

@ -0,0 +1,7 @@
package com.tangem.tap.domain.scanCard.chains
import arrow.core.Either
import com.tangem.domain.card.ScanCardException
import com.tangem.domain.models.scan.ScanResponse
internal typealias ScanChainResult = Either<ScanCardException, ScanResponse>

View file

@ -1,25 +0,0 @@
package com.tangem.tap.domain.scanCard.chains
import arrow.core.Either
import arrow.core.right
import com.tangem.domain.card.ScanCardException
import com.tangem.domain.core.chain.Chain
import com.tangem.domain.models.scan.ScanResponse
/**
* Responsible for invoking the callback at the end of the card scanning operation. Should be passed as last chain in
* after card scanning chains. Always returns result of previous chain.
*
* @param onScanningFinished a suspending function to be called when the scanning process has finished.
*
* @see Chain for more information about the Chain interface.
*/
internal class ScanningFinishedChain(
private val onScanningFinished: suspend () -> Unit,
) : Chain<ScanCardException.ChainException, ScanResponse> {
override suspend fun invoke(previousChainResult: ScanResponse): Either<ScanChainException, ScanResponse> {
onScanningFinished()
return previousChainResult.right()
}
}

View file

@ -1,32 +1,27 @@
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
// TODO: Move to the :data:card module
internal class DefaultScanCardRepository(
private val tangemSdkManager: TangemSdkManager,
) : ScanCardRepository {
private val exceptionConverter = ScanCardExceptionConverter()
override suspend fun scanCard(
cardId: String?,
allowRequestAccessCodeFromStorage: Boolean,
): Either<ScanCardException, ScanResponse> = either {
when (
override suspend fun scanCard(cardId: String?, allowRequestAccessCodeFromStorage: Boolean): ScanResponse {
return when (
val result = tangemSdkManager.scanProduct(
cardId = cardId,
allowsRequestAccessCodeFromRepository = allowRequestAccessCodeFromStorage,
)
) {
is CompletionResult.Success -> result.data
is CompletionResult.Failure -> raise(exceptionConverter.convert(result.error))
is CompletionResult.Failure -> throw exceptionConverter.convert(result.error)
}
}
}

View file

@ -18,4 +18,5 @@ internal data class UserWalletPublicInformation(
val cardsInWallet: Set<String>,
val scanResponse: ScanResponse,
val isMultiCurrency: Boolean,
val hasBackupError: Boolean = false,
)

View file

@ -20,6 +20,7 @@ internal val UserWallet.publicInformation: UserWalletPublicInformation
wallets = emptyList(),
),
),
hasBackupError = hasBackupError,
)
internal fun UserWalletPublicInformation.toUserWallet(): UserWallet {
@ -30,6 +31,7 @@ internal fun UserWalletPublicInformation.toUserWallet(): UserWallet {
cardsInWallet = cardsInWallet,
scanResponse = scanResponse,
isMultiCurrency = isMultiCurrency,
hasBackupError = hasBackupError,
)
}