Updated on 2026-08-14
This commit is contained in:
commit
2bab9057fa
15 changed files with 376 additions and 104 deletions
|
|
@ -13,6 +13,8 @@ dependencies {
|
|||
implementation(files("libs/walletconnect-1.5.6.aar"))
|
||||
implementation(project(":domain:legacy"))
|
||||
implementation(project(":domain:models"))
|
||||
implementation(project(":domain:core"))
|
||||
implementation(project(":domain:card"))
|
||||
implementation(project(":common"))
|
||||
implementation(project(":core:analytics"))
|
||||
implementation(project(":core:featuretoggles"))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
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.AnalyticsEvent
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor
|
||||
|
||||
/**
|
||||
* Handles the analytics event interception and sending event after the scan card operation. Always returns result of
|
||||
* previous chain.
|
||||
*
|
||||
* @param event the analytics event to be handled by this chain.
|
||||
*
|
||||
* @see Chain for more information about the Chain interface.
|
||||
*/
|
||||
class AnalyticsChain(
|
||||
private val event: AnalyticsEvent,
|
||||
) : Chain<ScanCardException.ChainException, ScanResponse> {
|
||||
|
||||
override suspend fun invoke(
|
||||
previousChainResult: ScanResponse,
|
||||
): Either<ScanCardException.ChainException, ScanResponse> {
|
||||
val interceptor = CardContextInterceptor(previousChainResult)
|
||||
val params = event.params.toMutableMap()
|
||||
interceptor.intercept(params)
|
||||
event.params = params.toMap()
|
||||
|
||||
Analytics.send(event)
|
||||
|
||||
return previousChainResult.right()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
package com.tangem.tap.domain.scanCard.chains
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.data.source.preferences.PreferencesDataSource
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
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.core.chain.Chain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.common.extensions.addContext
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
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.delay
|
||||
import org.rekotlin.Store
|
||||
|
||||
/**
|
||||
* Handles the verification process to determine if the scanned card requires onboarding.
|
||||
*
|
||||
* Returns:
|
||||
* - [ScanChainException.OnboardingNeeded] if onboarding required for none SaltPay cards.
|
||||
* - [ScanChainException.CheckForSaltPayOnboardingCaseException] if unable to check for SaltPay onboarding case.
|
||||
* - [ScanChainException.PutSaltPayVisaCard] if scanned SaltPay card is not activated and onboarding required.
|
||||
*
|
||||
* @param store the [Store] that holds the state of the app.
|
||||
* @param tapWalletManager manager responsible for handling operations related to the Wallet.
|
||||
* @param preferencesStore data source for user preferences.
|
||||
*
|
||||
* @see Chain for more information about the Chain interface.
|
||||
*/
|
||||
class CheckForOnboardingChain(
|
||||
private val store: Store<AppState>,
|
||||
private val tapWalletManager: TapWalletManager,
|
||||
private val preferencesStore: PreferencesDataSource,
|
||||
) : Chain<ScanCardException.ChainException, ScanResponse> {
|
||||
|
||||
override suspend fun invoke(
|
||||
previousChainResult: ScanResponse,
|
||||
): Either<ScanCardException.ChainException, ScanResponse> {
|
||||
val cardTypesResolver = previousChainResult.cardTypesResolver
|
||||
|
||||
tapWalletManager.updateConfigManager(previousChainResult)
|
||||
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(previousChainResult))
|
||||
|
||||
return when {
|
||||
cardTypesResolver.isSaltPayVisa() -> {
|
||||
val manager = SaltPayActivationManagerFactory(
|
||||
blockchain = previousChainResult.cardTypesResolver.getBlockchain(),
|
||||
card = previousChainResult.card,
|
||||
).create()
|
||||
val result = OnboardingSaltPayHelper.isOnboardingCase(previousChainResult, manager)
|
||||
delay(timeMillis = 500)
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
Analytics.addContext(previousChainResult)
|
||||
val isOnboardingCase = result.data
|
||||
if (isOnboardingCase) {
|
||||
store.dispatch(
|
||||
GlobalAction.Onboarding.Start(previousChainResult, canSkipBackup = false),
|
||||
)
|
||||
store.dispatch(OnboardingSaltPayAction.SetDependencies(manager))
|
||||
store.dispatch(OnboardingSaltPayAction.Update(withAnalytics = false))
|
||||
ScanChainException.OnboardingNeeded(AppScreen.OnboardingWallet).left()
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
previousChainResult.right()
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
SaltPayExceptionHandler.handle(result.error)
|
||||
ScanChainException.CheckForSaltPayOnboardingCaseException(result.error).left()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cardTypesResolver.isSaltPayWallet() -> {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
if (previousChainResult.card.backupStatus?.isActive == false) {
|
||||
SaltPayExceptionHandler.handle(SaltPayActivationError.PutVisaCard)
|
||||
ScanChainException.PutSaltPayVisaCard.left()
|
||||
} else {
|
||||
Analytics.setContext(previousChainResult)
|
||||
previousChainResult.right()
|
||||
}
|
||||
}
|
||||
OnboardingHelper.isOnboardingCase(previousChainResult) -> {
|
||||
Analytics.addContext(previousChainResult)
|
||||
store.dispatchOnMain(GlobalAction.Onboarding.Start(previousChainResult, canSkipBackup = true))
|
||||
val appScreen = OnboardingHelper.whereToNavigate(previousChainResult)
|
||||
ScanChainException.OnboardingNeeded(appScreen).left()
|
||||
}
|
||||
else -> {
|
||||
Analytics.setContext(previousChainResult)
|
||||
// If twins was twinned previously but twins welcome not shown
|
||||
if (previousChainResult.twinsIsTwinned() && !preferencesStore.wasTwinsOnboardingShown()) {
|
||||
store.dispatchOnMain(
|
||||
TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(previousChainResult)),
|
||||
)
|
||||
ScanChainException.OnboardingNeeded(AppScreen.OnboardingTwins).left()
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
previousChainResult.right()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.tap.domain.scanCard.chains
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa
|
||||
|
||||
/**
|
||||
* Handles the verification for unfinished SaltPay backup processes after the card scanning operation.
|
||||
*
|
||||
* Returns [ScanChainException.PutSaltPayVisaCard] if backup is not finished.
|
||||
*
|
||||
* @param backupService a service responsible for managing backup operations, used here to check for unfinished backups
|
||||
* and verify the primary card type.
|
||||
*
|
||||
* @see Chain for more information about the Chain interface.
|
||||
*/
|
||||
internal class CheckForUnfinishedSaltPayBackupChain(
|
||||
private val backupService: BackupService,
|
||||
) : Chain<ScanCardException.ChainException, ScanResponse> {
|
||||
|
||||
override suspend fun invoke(
|
||||
previousChainResult: ScanResponse,
|
||||
): Either<ScanCardException.ChainException, ScanResponse> {
|
||||
if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) {
|
||||
return previousChainResult.right()
|
||||
}
|
||||
|
||||
val isTheSamePrimaryCard = backupService.primaryCardId
|
||||
?.let { it == previousChainResult.card.cardId }
|
||||
?: false
|
||||
|
||||
return if (previousChainResult.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) {
|
||||
ScanChainException.PutSaltPayVisaCard.left()
|
||||
} else {
|
||||
previousChainResult.right()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.tap.domain.scanCard.chains
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import com.tangem.tap.features.disclaimer.createDisclaimer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.rekotlin.Store
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
/**
|
||||
* Handles disclaimer display after the card scanning operation.
|
||||
*
|
||||
* Returns [ScanChainException.DisclaimerWasCanceled] if the disclaimer is dismissed by the user.
|
||||
*
|
||||
* @param store the [Store] that holds the state of the app, used here to dispatch actions related to disclaimers.
|
||||
* @param disclaimerWillShow an optional function to be invoked when a disclaimer is about to be shown. Default is an
|
||||
* empty function.
|
||||
*
|
||||
* @see Chain for more information about the Chain interface.
|
||||
*/
|
||||
internal class DisclaimerChain(
|
||||
private val store: Store<AppState>,
|
||||
private val disclaimerWillShow: () -> Unit = {},
|
||||
) : Chain<ScanCardException.ChainException, ScanResponse> {
|
||||
|
||||
override suspend fun invoke(
|
||||
previousChainResult: ScanResponse,
|
||||
): Either<ScanCardException.ChainException, ScanResponse> {
|
||||
val disclaimer = previousChainResult.card.createDisclaimer()
|
||||
|
||||
return if (disclaimer.isAccepted()) {
|
||||
previousChainResult.right()
|
||||
} else {
|
||||
disclaimerWillShow()
|
||||
showDisclaimer(disclaimer, previousChainResult)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun showDisclaimer(
|
||||
disclaimer: Disclaimer,
|
||||
response: ScanResponse,
|
||||
): Either<ScanCardException.ChainException, ScanResponse> {
|
||||
store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer))
|
||||
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
store.dispatchOnMain(
|
||||
DisclaimerAction.Show(
|
||||
fromScreen = AppScreen.Home,
|
||||
callback = DisclaimerCallback(
|
||||
onAccept = {
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(response.right())
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(ScanChainException.DisclaimerWasCanceled.left())
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.tap.domain.scanCard.chains
|
||||
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
|
||||
sealed class ScanChainException : ScanCardException.ChainException() {
|
||||
|
||||
/**
|
||||
* May be returned from [CheckForOnboardingChain] and [CheckForUnfinishedSaltPayBackupChain]
|
||||
* */
|
||||
object PutSaltPayVisaCard : ScanChainException()
|
||||
|
||||
/**
|
||||
* May be returned from [DisclaimerChain]
|
||||
* */
|
||||
object DisclaimerWasCanceled : ScanChainException()
|
||||
|
||||
/**
|
||||
* May be returned from [CheckForOnboardingChain]
|
||||
*
|
||||
* @param onboardingRoute route where to navigate
|
||||
* */
|
||||
data class OnboardingNeeded(val onboardingRoute: AppScreen) : ScanChainException()
|
||||
|
||||
/**
|
||||
* May be returned from [CheckForOnboardingChain]
|
||||
*
|
||||
* @param cause cause of exception
|
||||
* */
|
||||
data class CheckForSaltPayOnboardingCaseException(override val cause: Throwable?) : ScanChainException()
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
|
|
@ -10,9 +9,4 @@ dependencies {
|
|||
implementation(project(":domain:models"))
|
||||
|
||||
implementation(deps.tangem.card.core)
|
||||
|
||||
implementation(deps.hilt.core)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
implementation(deps.moshi.kotlin)
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
package com.tangem.domain.card
|
||||
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
|
||||
// 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 SdkException(override val cause: TangemSdkError) : ScanCardException()
|
||||
class UnknownException(override val cause: Exception) : ScanCardException()
|
||||
}
|
||||
|
|
@ -1,16 +1,15 @@
|
|||
package com.tangem.domain.card
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.EitherNel
|
||||
import arrow.core.flatMap
|
||||
import arrow.core.toEitherNel
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.domain.card.model.ScanCardParams
|
||||
import com.tangem.domain.card.repository.ScanCardRepository
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.core.chain.ChainProcessor
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Use case responsible for scanning a card and returning a [ScanResponse] object.
|
||||
|
|
@ -18,7 +17,7 @@ import javax.inject.Inject
|
|||
* @property tangemSdk An instance of [TangemSdk] to configure the display format of the card ID.
|
||||
* @constructor Create a new instance of [ScanCardUseCase] with the given dependencies.
|
||||
*/
|
||||
class ScanCardUseCase @Inject internal constructor(
|
||||
class ScanCardUseCase(
|
||||
private val scanCardRepository: ScanCardRepository,
|
||||
private val tangemSdk: TangemSdk,
|
||||
) {
|
||||
|
|
@ -32,23 +31,31 @@ class ScanCardUseCase @Inject internal constructor(
|
|||
|
||||
/**
|
||||
* Scan a card and return a [ScanResponse] object.
|
||||
* @param params An instance of [ScanCardParams] to configure the scan process.
|
||||
* @param cardId an optional card ID to scan. If null, can scan any card present.
|
||||
* 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.
|
||||
* Defaults to an empty array.
|
||||
* @return A [EitherNel] object with either a non-empty list of [ScanCardException] or a [ScanResponse].
|
||||
*/
|
||||
suspend operator fun invoke(params: ScanCardParams = ScanCardParams()): EitherNel<ScanCardException, ScanResponse> {
|
||||
suspend operator fun invoke(
|
||||
cardId: String? = null,
|
||||
allowRequestAccessCodeFromStorage: Boolean = false,
|
||||
afterScanChains: Array<Chain<ScanCardException.ChainException, ScanResponse>> = emptyArray(),
|
||||
): Either<ScanCardException, ScanResponse> {
|
||||
resetCardIdDisplayFormat()
|
||||
scanChainProcessor.addChains(*params.afterScanChains)
|
||||
scanChainProcessor.addChains(afterScanChains)
|
||||
|
||||
return scanCardRepository.scanCard(
|
||||
cardId = params.cardId,
|
||||
allowRequestAccessCodeFromRepository = params.allowRequestAccessCodeFromRepository,
|
||||
cardId = cardId,
|
||||
allowRequestAccessCodeFromStorage = allowRequestAccessCodeFromStorage,
|
||||
)
|
||||
.onRight { scanResponse ->
|
||||
updateCardIdDisplayFormat(scanResponse.productType)
|
||||
}
|
||||
.toEitherNel()
|
||||
.flatMap { scanResponse ->
|
||||
scanChainProcessor.launchChains(initial = scanResponse)
|
||||
.flatMap { response ->
|
||||
scanChainProcessor.launchChains(initial = response)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.domain.card.di
|
||||
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.domain.card.repository.ScanCardRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class CardDomainModule {
|
||||
|
||||
@Provides
|
||||
fun provideScanCardUseCase(scanCardRepository: ScanCardRepository, tangemSdk: TangemSdk) =
|
||||
ScanCardUseCase(scanCardRepository, tangemSdk)
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.domain.card.model
|
||||
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
||||
/**
|
||||
* Represents the parameters to be used during a card scanning operation.
|
||||
*
|
||||
* @property cardId an optional card ID to scan. If null, can scan any card present.
|
||||
* Defaults to null.
|
||||
* @property allowRequestAccessCodeFromRepository whether to prompt the user for an access code if needed.
|
||||
* Defaults to false.
|
||||
* @property afterScanChains An array of chains that should be executed after a successful card scan operation.
|
||||
* Defaults to an empty array.
|
||||
*/
|
||||
class ScanCardParams(
|
||||
val cardId: String? = null,
|
||||
val allowRequestAccessCodeFromRepository: Boolean = false,
|
||||
val afterScanChains: Array<out Chain<ScanCardException.ChainException, ScanResponse>> = emptyArray(),
|
||||
)
|
||||
|
|
@ -10,15 +10,15 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
interface ScanCardRepository {
|
||||
|
||||
/**
|
||||
* Scans the card with the given [cardId] and returns a [ScanResponse]. If [allowRequestAccessCodeFromRepository]
|
||||
* is true, the repository may prompt the user for an access code.
|
||||
* Scans the card with the given [cardId] and returns a [ScanResponse]
|
||||
*
|
||||
* @param cardId an optional card ID to scan. If null, the repository should scan any card present.
|
||||
* @param allowRequestAccessCodeFromRepository whether to prompt the user for an access code if needed.
|
||||
* @param allowRequestAccessCodeFromStorage whether the access code can be requested from
|
||||
* internal storage during the scan process
|
||||
* @return an [Either] that contains a [ScanCardException] in case of an error or a [ScanResponse].
|
||||
*/
|
||||
suspend fun scanCard(
|
||||
cardId: String?,
|
||||
allowRequestAccessCodeFromRepository: Boolean,
|
||||
allowRequestAccessCodeFromStorage: Boolean,
|
||||
): Either<ScanCardException, ScanResponse>
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.domain.core.chain
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.EitherNel
|
||||
|
||||
/**
|
||||
* A chain in the [ChainProcessor] class for processing a chain of operations with the ability to handle errors.
|
||||
|
|
@ -12,8 +11,8 @@ interface Chain<E, R> {
|
|||
|
||||
/**
|
||||
* Invokes the chain with the previous chain result as input and returns an [Either] result.
|
||||
* @param previousChainResult the previous chain result as an [EitherNel]
|
||||
* @param previousChainResult the previous chain result as an [R]
|
||||
* @return the result of the chain processing as an [Either]
|
||||
*/
|
||||
suspend operator fun invoke(previousChainResult: EitherNel<E, R>): Either<E, R>
|
||||
suspend operator fun invoke(previousChainResult: R): Either<E, R>
|
||||
}
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
package com.tangem.domain.core.chain
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.EitherNel
|
||||
import arrow.core.left
|
||||
import arrow.core.nel
|
||||
import arrow.core.right
|
||||
import arrow.core.toEitherNel
|
||||
import arrow.core.raise.either
|
||||
|
||||
// TODO: Create Ior implementation
|
||||
// TODO: Create accumulate implementation
|
||||
/**
|
||||
* A class for processing a chain of operations with the ability to handle errors.
|
||||
* @param E the type of error
|
||||
|
|
@ -23,41 +21,15 @@ 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(vararg chains: Chain<E, R>) {
|
||||
fun addChains(chains: Array<Chain<E, R>>) {
|
||||
this.chains.addAll(chains)
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the chains with the given initial value as input.
|
||||
* @param initial the initial value to start the chain processing.
|
||||
* @param accumulateExceptions determines whether to accumulate exceptions or break on first exception.
|
||||
* Defaults to true.
|
||||
* @return the result of the chain processing as an EitherNel, which is a disjunction that may contain multiple errors.
|
||||
*/
|
||||
private suspend fun launchChains(initial: Either<E, R>, accumulateExceptions: Boolean = true): EitherNel<E, R> {
|
||||
return chains.fold(initial.toEitherNel()) { previousChainResult, chain ->
|
||||
chain.invoke(previousChainResult)
|
||||
.mapLeft { e ->
|
||||
previousChainResult.leftOrNull()?.plus(e)
|
||||
?: e.nel()
|
||||
}
|
||||
.onLeft { e ->
|
||||
if (!accumulateExceptions) {
|
||||
return e.left()
|
||||
}
|
||||
}
|
||||
suspend fun launchChains(initial: R): Either<E, R> {
|
||||
return either {
|
||||
chains.fold(initial) { prevChainResult, chain ->
|
||||
chain.invoke(prevChainResult).bind()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the chains with the given initial value as input.
|
||||
* This function is a convenience function that assumes the initial value does not contain any errors.
|
||||
* @param initial the initial value to start the chain processing.
|
||||
* @param accumulateExceptions determines whether to accumulate exceptions or break on first exception.
|
||||
* Defaults to true.
|
||||
* @return the result of the chain processing as an EitherNel, which is a disjunction that may contain multiple errors.
|
||||
*/
|
||||
suspend fun launchChains(initial: R, accumulateExceptions: Boolean = true): EitherNel<E, R> {
|
||||
return launchChains(initial.right(), accumulateExceptions)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue