Updated on 2026-08-14

This commit is contained in:
Tangem 2023-05-03 00:29:14 +03:00
parent abc5ff5968
commit ace7eba11f
8 changed files with 37 additions and 104 deletions

View file

@ -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)
}

View file

@ -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()
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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(),
)

View file

@ -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>
}

View file

@ -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>
}

View file

@ -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)
}
}