Updated on 2026-08-14
This commit is contained in:
parent
bc00198797
commit
ddb8de4964
20 changed files with 447 additions and 84 deletions
|
|
@ -55,9 +55,8 @@ internal object TokensDomainModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetTokenListUseCase {
|
||||
return GetTokenListUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers)
|
||||
return GetTokenListUseCase(currenciesRepository, quotesRepository, networksRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -66,9 +65,8 @@ internal object TokensDomainModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetCardTokensListUseCase {
|
||||
return GetCardTokensListUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers)
|
||||
return GetCardTokensListUseCase(currenciesRepository, quotesRepository, networksRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
package com.tangem.domain.core.lce
|
||||
|
||||
import arrow.core.identity
|
||||
import com.tangem.domain.core.utils.flatMap
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
|
||||
/**
|
||||
* A sealed class representing the three states of a data load operation: Loading, Content, and Error.
|
||||
*
|
||||
* @param E The type of the error object.
|
||||
* @param C The type of the content object.
|
||||
*/
|
||||
sealed class Lce<out E : Any, out C : Any> {
|
||||
|
||||
/**
|
||||
* Represents the loading state, which may contain partial content.
|
||||
*
|
||||
* @param partialContent The partial content that has been loaded so far, if any.
|
||||
*/
|
||||
data class Loading<C : Any>(val partialContent: C?) : Lce<Nothing, C>()
|
||||
|
||||
/**
|
||||
* Represents the content state, which contains the loaded content.
|
||||
*
|
||||
* @param content The loaded content.
|
||||
*/
|
||||
data class Content<C : Any>(val content: C) : Lce<Nothing, C>()
|
||||
|
||||
/**
|
||||
* Represents the error state, which contains an error object.
|
||||
*
|
||||
* @param error The error that occurred during loading.
|
||||
*/
|
||||
data class Error<E : Any>(val error: E) : Lce<E, Nothing>()
|
||||
|
||||
/**
|
||||
* Applies the given functions to the content, error, or partial content of this Lce, depending on its state.
|
||||
*
|
||||
* @param ifLoading The function to apply if this is a [Loading] state.
|
||||
* @param ifContent The function to apply if this is a [Content] state.
|
||||
* @param ifError The function to apply if this is an [Error] state.
|
||||
* @return The result of applying the corresponding function.
|
||||
*/
|
||||
inline fun <T> fold(
|
||||
ifLoading: (partialContent: C?) -> T,
|
||||
ifContent: (content: C) -> T,
|
||||
ifError: (error: E) -> T,
|
||||
): T = when (this) {
|
||||
is Loading -> ifLoading(partialContent)
|
||||
is Error -> ifError(error)
|
||||
is Content -> ifContent(content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the content of this [Lce] by applying the given function.
|
||||
* If this is a [Content] state, the function is applied to the [Content.content].
|
||||
* If this is a [Loading] state and partialContent is present,
|
||||
* the function is applied to the [Loading.partialContent].
|
||||
*
|
||||
* @param ifContent The function to apply to the content or partial content.
|
||||
* @return A new [Lce] instance containing the result of applying the function.
|
||||
*/
|
||||
inline fun <T : Any> map(ifContent: (C) -> T): Lce<E, T> = flatMap { content, isLoading ->
|
||||
if (isLoading) {
|
||||
lceLoading(ifContent(content))
|
||||
} else {
|
||||
ifContent(content).lceContent()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the error of this [Lce] by applying the given function, if this is an [Error] state.
|
||||
*
|
||||
* @param ifError The function to apply to the error.
|
||||
* @return A new [Lce] with the transformed error, or this [Lce] unchanged if it is not an [Error] state.
|
||||
*/
|
||||
inline fun <T : Any> mapError(ifError: (E) -> T): Lce<T, C> = when (this) {
|
||||
is Loading -> this
|
||||
is Content -> this
|
||||
is Error -> ifError(error).lceError()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of this [Lce] if it's a [Lce.Content] or partial content if it's a [Lce.Loading],
|
||||
* `null` if it's a [Lce.Error].
|
||||
*
|
||||
* @return The content of this [Lce] or `null`.
|
||||
*/
|
||||
fun getOrNull(): C? = fold(
|
||||
ifLoading = ::identity,
|
||||
ifContent = ::identity,
|
||||
ifError = { null },
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package com.tangem.domain.core.lce
|
||||
|
||||
import arrow.core.raise.Raise
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.channels.ProducerScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.experimental.ExperimentalTypeInference
|
||||
|
||||
/**
|
||||
* A [Flow] of [Lce]
|
||||
*
|
||||
* @param E The type of the error object.
|
||||
* @param C The type of the content object.
|
||||
* */
|
||||
typealias LceFlow<E, C> = Flow<Lce<E, C>>
|
||||
|
||||
/**
|
||||
* A class that wraps a [LceRaise] instance for [Lce] type within a [ProducerScope].
|
||||
* It provides methods to handle [Lce] instances and raise errors within a [Flow].
|
||||
*
|
||||
* @property raise The [LceRaise] instance that this class wraps.
|
||||
* @property scope The [ProducerScope] that this class operates within.
|
||||
* @property ifLoading The function to call if a loading state is raised.
|
||||
*/
|
||||
class LceFlowScope<E : Any, C : Any> @PublishedApi internal constructor(
|
||||
private val raise: LceRaise<E>,
|
||||
private val scope: ProducerScope<Lce<E, C>>,
|
||||
private val ifLoading: LceRaise<E>.(C) -> Lce<E, C>,
|
||||
) : Raise<Lce<E, Nothing>> by raise,
|
||||
CoroutineScope by scope {
|
||||
|
||||
/**
|
||||
* Raises an [Lce] instance within the [ProducerScope].
|
||||
* It closes the [ProducerScope] after raise.
|
||||
*
|
||||
* @param r The [Lce] instance to raise.
|
||||
*/
|
||||
override fun raise(r: Lce<E, Nothing>): Nothing {
|
||||
scope.launch(NonCancellable) {
|
||||
scope.send(r)
|
||||
scope.close()
|
||||
}
|
||||
|
||||
raise.raise(r)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a content value within the [ProducerScope].
|
||||
* If the content is still loading, it calls [ifLoading] lambda to retrieve a state.
|
||||
* Otherwise, it wraps the content in a [Lce.Content] state.
|
||||
*
|
||||
* @param content The content value to send.
|
||||
* @param isStillLoading A flag indicating whether the content is still loading.
|
||||
*/
|
||||
suspend fun send(content: C, isStillLoading: Boolean = false) {
|
||||
val value = if (isStillLoading) {
|
||||
ifLoading(raise, content)
|
||||
} else {
|
||||
content.lceContent()
|
||||
}
|
||||
|
||||
scope.send(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [LceFlow] by executing the given [block] within a [LceFlowScope] context.
|
||||
*
|
||||
* Flow starts with a [Lce.Loading] state.
|
||||
*
|
||||
* @param ifLoading The function to call if the [block] raises a [Lce.Loading] state.
|
||||
* By default, it creates a new [Lce.Loading] state with the value returned by the [block].
|
||||
* @param block The block to execute within a [LceFlowScope] context.
|
||||
* @return A [LceFlow] representing the result of the [block].
|
||||
*/
|
||||
@OptIn(ExperimentalTypeInference::class)
|
||||
fun <E : Any, C : Any> lceFlow(
|
||||
ifLoading: LceRaise<E>.(C) -> Lce<E, C> = { lceLoading(partialContent = it) },
|
||||
@BuilderInference block: suspend LceFlowScope<E, C>.() -> Unit,
|
||||
): LceFlow<E, C> {
|
||||
return channelFlow {
|
||||
trySend(lceLoading())
|
||||
|
||||
lce {
|
||||
val scope = LceFlowScope(
|
||||
raise = this@lce,
|
||||
scope = this@channelFlow,
|
||||
ifLoading = ifLoading,
|
||||
)
|
||||
|
||||
block(scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.tangem.domain.core.lce
|
||||
|
||||
import arrow.atomic.Atomic
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.recover
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import kotlin.experimental.ExperimentalTypeInference
|
||||
|
||||
/**
|
||||
* A class that wraps a [Raise] instance for [Lce] type.
|
||||
* It provides methods to handle [Lce] instances and raise errors.
|
||||
*
|
||||
* @property raise The [Raise] instance that this class wraps.
|
||||
* @property isLoading An [Atomic] boolean flag indicating whether a loading operation is in progress.
|
||||
*/
|
||||
class LceRaise<E : Any> @PublishedApi internal constructor(
|
||||
private val raise: Raise<Lce<E, Nothing>>,
|
||||
) : Raise<Lce<E, Nothing>> by raise {
|
||||
|
||||
val isLoading: Atomic<Boolean> = Atomic(false)
|
||||
|
||||
/**
|
||||
* Binds the content of this [Lce] instance and handles its state.
|
||||
* If this is a [Lce.Loading] state, sets the [isLoading] flag to true and calls the [ifLoading] function.
|
||||
* If this is a [Lce.Content] state, returns the content.
|
||||
* If this is a [Lce.Error] state, raises the error.
|
||||
*
|
||||
* @param ifLoading The function to call if this is a [Lce.Loading] state.
|
||||
* By default, it raises a new [Lce.Loading] state.
|
||||
* @return The content of this [Lce] instance.
|
||||
*/
|
||||
fun <C : Any> Lce<E, C>.bind(ifLoading: (partialContent: C?) -> C = { raise(lceLoading()) }): C = when (this) {
|
||||
is Lce.Loading -> {
|
||||
isLoading.set(true)
|
||||
|
||||
ifLoading(partialContent)
|
||||
}
|
||||
is Lce.Content -> content
|
||||
is Lce.Error -> raise(r = this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the content of this [Lce] instance and handles its state.
|
||||
* If this is a [Lce.Loading] state, sets the [isLoading] flag to true and returns the partial content.
|
||||
* If this is a [Lce.Content] state, returns the content.
|
||||
* If this is a [Lce.Error] state, raises the error.
|
||||
*
|
||||
* @return The content of this [Lce] instance.
|
||||
*/
|
||||
fun <C : Any> Lce<E, C>.bindOrNull(): C? = when (this) {
|
||||
is Lce.Loading -> {
|
||||
isLoading.set(true)
|
||||
|
||||
partialContent
|
||||
}
|
||||
is Lce.Content -> content
|
||||
is Lce.Error -> raise(r = this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [Lce] instance by executing the given [block] within a [LceRaise] context.
|
||||
*
|
||||
* @param ifLoading The function to call if the [block] raises a [Lce.Loading] state.
|
||||
* By default, it creates a new [Lce.Loading] state with the value returned by the [block].
|
||||
* @param block The block to execute within a [LceRaise] context.
|
||||
* @return A [Lce] instance representing the result of the [block].
|
||||
*/
|
||||
@OptIn(ExperimentalTypeInference::class)
|
||||
inline fun <E : Any, C : Any> lce(
|
||||
ifLoading: LceRaise<E>.(C) -> Lce<E, C> = { lceLoading(partialContent = it) },
|
||||
@BuilderInference block: LceRaise<E>.() -> C,
|
||||
): Lce<E, C> = recover(
|
||||
block = {
|
||||
val raise = LceRaise(raise = this)
|
||||
val value = block(raise)
|
||||
|
||||
if (raise.isLoading.get()) {
|
||||
ifLoading(raise, value)
|
||||
} else {
|
||||
value.lceContent()
|
||||
}
|
||||
},
|
||||
recover = { e: Lce<E, Nothing> -> e },
|
||||
)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.domain.core.utils
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* [Flow] of [Either]
|
||||
*
|
||||
* @param E type of left value
|
||||
* @param A type of right value
|
||||
* */
|
||||
typealias EitherFlow<E, A> = Flow<Either<E, A>>
|
||||
|
||||
/**
|
||||
* Converts an [Either] instance to a [Lce] instance.
|
||||
* If this is a [Either.Left], it is converted to a [Lce.Error] with the same error.
|
||||
* If this is a [Either.Right], it is converted to a [Lce.Content] or [Lce.Loading] with the same content,
|
||||
* depending on the [isStillLoading] parameter.
|
||||
*
|
||||
* @param isStillLoading A flag indicating whether the content is still loading.
|
||||
* If true, the [Either.Right] is converted to a [Lce.Loading].
|
||||
* @return A [Lce] instance containing the same content or error as this [Either],
|
||||
* and possibly indicating a loading state.
|
||||
*/
|
||||
inline fun <reified E : Any, reified T : Any> Either<E, T>.toLce(isStillLoading: Boolean = false): Lce<E, T> {
|
||||
return when (this) {
|
||||
is Either.Left -> Lce.Error(value)
|
||||
is Either.Right -> {
|
||||
if (isStillLoading) {
|
||||
Lce.Loading(value)
|
||||
} else {
|
||||
Lce.Content(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.domain.core.utils
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.identity
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
|
||||
/**
|
||||
* Creates a [Lce.Loading] instance with optional partial content.
|
||||
*
|
||||
* @param partialContent The partial content that has been loaded so far, if any.
|
||||
* @return A [Lce.Loading] instance.
|
||||
*/
|
||||
fun <C : Any> lceLoading(partialContent: C? = null): Lce<Nothing, C> = Lce.Loading(partialContent)
|
||||
|
||||
/**
|
||||
* Wraps the receiver object in a [Lce.Content] instance.
|
||||
*
|
||||
* @return A [Lce.Content] instance containing the receiver object.
|
||||
*/
|
||||
fun <C : Any> C.lceContent(): Lce<Nothing, C> = Lce.Content(content = this)
|
||||
|
||||
/**
|
||||
* Wraps the receiver object in a [Lce.Error] instance.
|
||||
*
|
||||
* @return A [Lce.Error] instance containing the receiver object.
|
||||
*/
|
||||
fun <E : Any> E.lceError(): Lce<E, Nothing> = Lce.Error(error = this)
|
||||
|
||||
/**
|
||||
* Transforms this [Lce] instance by applying the given function into a new [Lce] instance.
|
||||
*
|
||||
* @param block The function to apply to the content of this [Lce].
|
||||
* @return A new [Lce] instance containing the result of applying the function.
|
||||
* */
|
||||
inline fun <E : Any, C : Any, T : Any> Lce<E, C>.flatMap(
|
||||
block: (content: C, isLoading: Boolean) -> Lce<E, T>,
|
||||
): Lce<E, T> = when (this) {
|
||||
is Lce.Loading -> partialContent?.let { block(it, true) } ?: lceLoading()
|
||||
is Lce.Content -> block(content, false)
|
||||
is Lce.Error -> this
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of this [Lce] if it's a [Lce.Content] or applies the given functions if it's a [Lce.Loading] or [Lce.Error].
|
||||
*
|
||||
* @param ifLoading The function to apply if this is a [Lce.Loading] state.
|
||||
* @param ifError The function to apply if this is a [Lce.Error] state.
|
||||
* @return The content of this [Lce] or the result of applying the corresponding function.
|
||||
*/
|
||||
inline fun <E : Any, C : Any> Lce<E, C>.getOrElse(ifLoading: (maybeContent: C?) -> C, ifError: (error: E) -> C): C {
|
||||
return fold(
|
||||
ifLoading = ifLoading,
|
||||
ifContent = ::identity,
|
||||
ifError = ifError,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms this [Lce] into an [Either] instance.
|
||||
* If this is a [Lce.Content], the content is wrapped in a [Either.Right].
|
||||
* If this is a [Lce.Error], the error is wrapped in a [Either.Left].
|
||||
* If this is a [Lce.Loading], the [ifLoading] function is applied to the partial content and the result is wrapped in a
|
||||
* [Either.Right].
|
||||
*
|
||||
* @param ifLoading The function to apply if this is a [Lce.Loading] state.
|
||||
* @return An [Either] instance containing the content or error of this [Lce],
|
||||
* or the result of applying the [ifLoading] function to the partial content.
|
||||
*/
|
||||
inline fun <E : Any, C : Any> Lce<E, C>.toEither(ifLoading: (maybeContent: C?) -> C): Either<E, C> = fold(
|
||||
ifLoading = { ifLoading(it).right() },
|
||||
ifContent = { it.right() },
|
||||
ifError = { it.left() },
|
||||
)
|
||||
|
|
@ -15,7 +15,7 @@ import java.math.BigDecimal
|
|||
*/
|
||||
data class CryptoCurrencyStatus(
|
||||
val currency: CryptoCurrency,
|
||||
val value: Status,
|
||||
val value: Value,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -23,7 +23,7 @@ data class CryptoCurrencyStatus(
|
|||
*
|
||||
* @property isError Indicates whether this status represents an error status.
|
||||
*/
|
||||
sealed class Status(val isError: Boolean) {
|
||||
sealed class Value(val isError: Boolean) {
|
||||
|
||||
/** The amount of the cryptocurrency. */
|
||||
open val amount: BigDecimal? = null
|
||||
|
|
@ -48,7 +48,7 @@ data class CryptoCurrencyStatus(
|
|||
}
|
||||
|
||||
/** Represents the Loading state of a cryptocurrency, typically while fetching its details. */
|
||||
object Loading : Status(isError = false)
|
||||
data object Loading : Value(isError = false)
|
||||
|
||||
/**
|
||||
* Represents a state where the cryptocurrency is not reachable.
|
||||
|
|
@ -61,19 +61,19 @@ data class CryptoCurrencyStatus(
|
|||
override val priceChange: BigDecimal?,
|
||||
override val fiatRate: BigDecimal?,
|
||||
override val networkAddress: NetworkAddress?,
|
||||
) : Status(isError = true)
|
||||
) : Value(isError = true)
|
||||
|
||||
/** Represents a state where the cryptocurrency's network amount not found. */
|
||||
data class NoAmount(
|
||||
override val priceChange: BigDecimal?,
|
||||
override val fiatRate: BigDecimal?,
|
||||
) : Status(isError = true)
|
||||
) : Value(isError = true)
|
||||
|
||||
/** Represents a state where the cryptocurrency's derivation is missed. */
|
||||
data class MissedDerivation(
|
||||
override val priceChange: BigDecimal?,
|
||||
override val fiatRate: BigDecimal?,
|
||||
) : Status(isError = true)
|
||||
) : Value(isError = true)
|
||||
|
||||
/**
|
||||
* Represents a state where there is no account associated with the cryptocurrency
|
||||
|
|
@ -86,7 +86,7 @@ data class CryptoCurrencyStatus(
|
|||
override val priceChange: BigDecimal?,
|
||||
override val fiatRate: BigDecimal?,
|
||||
override val networkAddress: NetworkAddress,
|
||||
) : Status(isError = false) {
|
||||
) : Value(isError = false) {
|
||||
|
||||
override val amount: BigDecimal = BigDecimal.ZERO
|
||||
}
|
||||
|
|
@ -110,7 +110,7 @@ data class CryptoCurrencyStatus(
|
|||
override val hasCurrentNetworkTransactions: Boolean,
|
||||
override val pendingTransactions: Set<TxHistoryItem>,
|
||||
override val networkAddress: NetworkAddress,
|
||||
) : Status(isError = false)
|
||||
) : Value(isError = false)
|
||||
|
||||
/**
|
||||
* Represents a Custom state of a cryptocurrency, typically used for user-defined tokens.
|
||||
|
|
@ -131,7 +131,7 @@ data class CryptoCurrencyStatus(
|
|||
override val hasCurrentNetworkTransactions: Boolean,
|
||||
override val pendingTransactions: Set<TxHistoryItem>,
|
||||
override val networkAddress: NetworkAddress,
|
||||
) : Status(isError = false)
|
||||
) : Value(isError = false)
|
||||
|
||||
/**
|
||||
* Represents a state where the cryptocurrency is available, but there is no current quote available for it.
|
||||
|
|
@ -146,5 +146,5 @@ data class CryptoCurrencyStatus(
|
|||
override val hasCurrentNetworkTransactions: Boolean,
|
||||
override val pendingTransactions: Set<TxHistoryItem>,
|
||||
override val networkAddress: NetworkAddress,
|
||||
) : Status(isError = false)
|
||||
) : Value(isError = false)
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import java.math.BigDecimal
|
|||
*/
|
||||
data class NetworkStatus(
|
||||
val network: Network,
|
||||
val value: Status,
|
||||
val value: Value,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -19,19 +19,19 @@ data class NetworkStatus(
|
|||
*
|
||||
* This sealed class includes different states like unreachable, missed derivation, verified, and no account.
|
||||
*/
|
||||
sealed class Status
|
||||
sealed class Value
|
||||
|
||||
/**
|
||||
* Represents the state where the network is unreachable.
|
||||
*
|
||||
* @property address Network addresses.
|
||||
*/
|
||||
data class Unreachable(val address: NetworkAddress?) : Status()
|
||||
data class Unreachable(val address: NetworkAddress?) : Value()
|
||||
|
||||
/**
|
||||
* Represents the state where a derivation has been missed.
|
||||
*/
|
||||
object MissedDerivation : Status()
|
||||
data object MissedDerivation : Value()
|
||||
|
||||
/**
|
||||
* Represents the verified state of the network, including the amounts associated with different cryptocurrencies
|
||||
|
|
@ -46,7 +46,7 @@ data class NetworkStatus(
|
|||
val address: NetworkAddress,
|
||||
val amounts: Map<CryptoCurrency.ID, CryptoCurrencyAmountStatus>,
|
||||
val pendingTransactions: Map<CryptoCurrency.ID, Set<TxHistoryItem>>,
|
||||
) : Status()
|
||||
) : Value()
|
||||
|
||||
/**
|
||||
* Represents the state where there is no account, and an amount is required to create one.
|
||||
|
|
@ -59,5 +59,5 @@ data class NetworkStatus(
|
|||
val address: NetworkAddress,
|
||||
val amountToCreateAccount: BigDecimal,
|
||||
val errorMessage: String,
|
||||
) : Status()
|
||||
) : Value()
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
|
@ -12,19 +12,19 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
|
||||
class GetCardTokensListUseCase(
|
||||
internal val currenciesRepository: CurrenciesRepository,
|
||||
internal val quotesRepository: QuotesRepository,
|
||||
internal val networksRepository: NetworksRepository,
|
||||
internal val dispatchers: CoroutineDispatcherProvider,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
operator fun invoke(userWalletId: UserWalletId): Flow<Either<TokenListError, TokenList>> {
|
||||
operator fun invoke(userWalletId: UserWalletId): EitherFlow<TokenListError, TokenList> {
|
||||
return getTokensStatuses(userWalletId).transformLatest { maybeTokens ->
|
||||
maybeTokens.fold(
|
||||
ifLeft = { error ->
|
||||
|
|
@ -37,9 +37,7 @@ class GetCardTokensListUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getTokensStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
): Flow<Either<TokenListError, List<CryptoCurrencyStatus>>> {
|
||||
private fun getTokensStatuses(userWalletId: UserWalletId): EitherFlow<TokenListError, List<CryptoCurrencyStatus>> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
userWalletId = userWalletId,
|
||||
currenciesRepository = currenciesRepository,
|
||||
|
|
@ -56,7 +54,7 @@ class GetCardTokensListUseCase(
|
|||
private fun createTokenList(
|
||||
userWalletId: UserWalletId,
|
||||
tokens: List<CryptoCurrencyStatus>,
|
||||
): Flow<Either<TokenListError, TokenList>> {
|
||||
): EitherFlow<TokenListError, TokenList> {
|
||||
val operations = TokenListOperations(
|
||||
userWalletId = userWalletId,
|
||||
tokens = tokens,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
|
|
@ -20,10 +19,9 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.transformLatest
|
||||
|
||||
class GetTokenListUseCase(
|
||||
internal val currenciesRepository: CurrenciesRepository,
|
||||
internal val quotesRepository: QuotesRepository,
|
||||
internal val networksRepository: NetworksRepository,
|
||||
internal val dispatchers: CoroutineDispatcherProvider,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
|
@ -45,7 +43,9 @@ class GetTokenListUseCase(
|
|||
): Flow<Either<TokenListError, List<CryptoCurrencyStatus>>> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
userWalletId = userWalletId,
|
||||
useCase = this@GetTokenListUseCase,
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
)
|
||||
|
||||
return operations.getCurrenciesStatusesFlow()
|
||||
|
|
@ -61,7 +61,7 @@ class GetTokenListUseCase(
|
|||
val operations = TokenListOperations(
|
||||
userWalletId = userWalletId,
|
||||
tokens = tokens,
|
||||
useCase = this@GetTokenListUseCase,
|
||||
currenciesRepository = currenciesRepository,
|
||||
)
|
||||
|
||||
return operations.getTokenListFlow().map { maybeTokenList ->
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.domain.tokens.operations
|
|||
|
||||
import arrow.core.*
|
||||
import arrow.core.raise.*
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
|
|
@ -20,18 +20,8 @@ internal class CurrenciesStatusesOperations(
|
|||
private val userWalletId: UserWalletId,
|
||||
) {
|
||||
|
||||
constructor(
|
||||
userWalletId: UserWalletId,
|
||||
useCase: GetTokenListUseCase,
|
||||
) : this(
|
||||
currenciesRepository = useCase.currenciesRepository,
|
||||
quotesRepository = useCase.quotesRepository,
|
||||
networksRepository = useCase.networksRepository,
|
||||
userWalletId = userWalletId,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun getCurrenciesStatusesFlow(): Flow<Either<Error, List<CryptoCurrencyStatus>>> {
|
||||
fun getCurrenciesStatusesFlow(): EitherFlow<Error, List<CryptoCurrencyStatus>> {
|
||||
return getMultiCurrencyWalletCurrencies().transformLatest { maybeCurrencies ->
|
||||
val nonEmptyCurrencies = maybeCurrencies.fold(
|
||||
ifLeft = { error ->
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ internal class CurrencyStatusOperations(
|
|||
|
||||
fun createTokenStatus(): CryptoCurrencyStatus = CryptoCurrencyStatus(currency, createStatus())
|
||||
|
||||
private fun createStatus(): CryptoCurrencyStatus.Status {
|
||||
private fun createStatus(): CryptoCurrencyStatus.Value {
|
||||
return when (val status = networkStatus?.value) {
|
||||
null -> CryptoCurrencyStatus.Loading
|
||||
is NetworkStatus.MissedDerivation -> createMissedDerivationStatus()
|
||||
|
|
@ -42,7 +42,7 @@ internal class CurrencyStatusOperations(
|
|||
networkAddress = status.address,
|
||||
)
|
||||
|
||||
private fun createStatus(status: NetworkStatus.Verified): CryptoCurrencyStatus.Status {
|
||||
private fun createStatus(status: NetworkStatus.Verified): CryptoCurrencyStatus.Value {
|
||||
val amount = when (val amount = status.amounts[currency.id]) {
|
||||
null -> {
|
||||
return CryptoCurrencyStatus.Loading
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import arrow.core.*
|
|||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.withError
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -18,16 +17,6 @@ internal class TokenListOperations(
|
|||
private val tokens: List<CryptoCurrencyStatus>,
|
||||
) {
|
||||
|
||||
constructor(
|
||||
userWalletId: UserWalletId,
|
||||
tokens: List<CryptoCurrencyStatus>,
|
||||
useCase: GetTokenListUseCase,
|
||||
) : this(
|
||||
currenciesRepository = useCase.currenciesRepository,
|
||||
userWalletId = userWalletId,
|
||||
tokens = tokens,
|
||||
)
|
||||
|
||||
fun getTokenListFlow(): Flow<Either<Error, TokenList>> {
|
||||
return combine(
|
||||
getIsGrouped(),
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import com.tangem.domain.tokens.repository.MockCurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.MockNetworksRepository
|
||||
import com.tangem.domain.tokens.repository.MockQuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -27,7 +26,6 @@ import org.junit.Test
|
|||
|
||||
internal class GetTokenListUseCaseTest {
|
||||
|
||||
private val dispatchers = TestingCoroutineDispatcherProvider()
|
||||
private val userWalletId = UserWalletId(value = null)
|
||||
|
||||
@Ignore
|
||||
|
|
@ -308,7 +306,6 @@ internal class GetTokenListUseCaseTest {
|
|||
isGrouped: Flow<Either<DataError, Boolean>> = flowOf(MockTokenLists.isGrouped.right()),
|
||||
isSortedByBalance: Flow<Either<DataError, Boolean>> = flowOf(MockTokenLists.isSortedByBalance.right()),
|
||||
) = GetTokenListUseCase(
|
||||
dispatchers = dispatchers,
|
||||
currenciesRepository = MockCurrenciesRepository(
|
||||
sortTokensResult = Unit.right(),
|
||||
removeCurrencyResult = Unit.right(),
|
||||
|
|
|
|||
|
|
@ -106,13 +106,11 @@ class SwapDomainModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetCardTokensListUseCase {
|
||||
return GetCardTokensListUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
}
|
||||
|
||||
private fun getMarketPriceState(
|
||||
status: CryptoCurrencyStatus.Status,
|
||||
status: CryptoCurrencyStatus.Value,
|
||||
currencySymbol: String,
|
||||
): MarketPriceBlockState {
|
||||
return when (status) {
|
||||
|
|
@ -106,7 +106,7 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.Status.toContentConfig(currencySymbol: String): MarketPriceBlockState.Content {
|
||||
private fun CryptoCurrencyStatus.Value.toContentConfig(currencySymbol: String): MarketPriceBlockState.Content {
|
||||
return MarketPriceBlockState.Content(
|
||||
currencySymbol = currencySymbol,
|
||||
price = formatPrice(status = this, appCurrency = appCurrencyProvider()),
|
||||
|
|
@ -117,11 +117,11 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getPriceChangeType(status: CryptoCurrencyStatus.Status): PriceChangeType {
|
||||
private fun getPriceChangeType(status: CryptoCurrencyStatus.Value): PriceChangeType {
|
||||
return PriceChangeConverter.fromBigDecimal(status.priceChange)
|
||||
}
|
||||
|
||||
private fun formatPriceChange(status: CryptoCurrencyStatus.Status): String {
|
||||
private fun formatPriceChange(status: CryptoCurrencyStatus.Value): String {
|
||||
val priceChange = status.priceChange ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatPercent(
|
||||
|
|
@ -130,7 +130,7 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun formatPrice(status: CryptoCurrencyStatus.Status, appCurrency: AppCurrency): String {
|
||||
private fun formatPrice(status: CryptoCurrencyStatus.Value, appCurrency: AppCurrency): String {
|
||||
val fiatRate = status.fiatRate ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
|
|
@ -140,7 +140,7 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun formatFiatAmount(status: CryptoCurrencyStatus.Status, appCurrency: AppCurrency): String {
|
||||
private fun formatFiatAmount(status: CryptoCurrencyStatus.Value, appCurrency: AppCurrency): String {
|
||||
val fiatAmount = status.fiatAmount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
|||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class SingleWalletCardStateConverter(
|
||||
private val status: CryptoCurrencyStatus.Status,
|
||||
private val status: CryptoCurrencyStatus.Value,
|
||||
private val selectedWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
) : Converter<WalletCardState, WalletCardState> {
|
||||
|
|
@ -50,7 +50,7 @@ internal class SingleWalletCardStateConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun WalletCardState.toContentState(status: CryptoCurrencyStatus.Status): WalletCardState {
|
||||
private fun WalletCardState.toContentState(status: CryptoCurrencyStatus.Value): WalletCardState {
|
||||
return WalletCardState.Content(
|
||||
id = id,
|
||||
title = title,
|
||||
|
|
@ -66,7 +66,7 @@ internal class SingleWalletCardStateConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun formatFiatAmount(status: CryptoCurrencyStatus.Status, appCurrency: AppCurrency): String {
|
||||
private fun formatFiatAmount(status: CryptoCurrencyStatus.Value, appCurrency: AppCurrency): String {
|
||||
val fiatAmount = status.fiatAmount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
|||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class SingleWalletMarketPriceConverter(
|
||||
private val status: CryptoCurrencyStatus.Status,
|
||||
private val status: CryptoCurrencyStatus.Value,
|
||||
private val appCurrency: AppCurrency,
|
||||
) : Converter<MarketPriceBlockState, MarketPriceBlockState> {
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ internal class SingleWalletMarketPriceConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun formatPrice(status: CryptoCurrencyStatus.Status, appCurrency: AppCurrency): String {
|
||||
private fun formatPrice(status: CryptoCurrencyStatus.Value, appCurrency: AppCurrency): String {
|
||||
val fiatRate = status.fiatRate ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
|
|
@ -54,13 +54,13 @@ internal class SingleWalletMarketPriceConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun formatPriceChange(status: CryptoCurrencyStatus.Status): String {
|
||||
private fun formatPriceChange(status: CryptoCurrencyStatus.Value): String {
|
||||
val priceChange = status.priceChange ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatPercent(percent = priceChange, useAbsoluteValue = true)
|
||||
}
|
||||
|
||||
private fun getPriceChangeType(status: CryptoCurrencyStatus.Status): PriceChangeType {
|
||||
private fun getPriceChangeType(status: CryptoCurrencyStatus.Value): PriceChangeType {
|
||||
return PriceChangeConverter.fromBigDecimal(status.priceChange)
|
||||
}
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
private fun sendToken(
|
||||
cryptoCurrency: CryptoCurrency.Token,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus.Status,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus.Value,
|
||||
feeCurrencyStatus: CryptoCurrencyStatus?,
|
||||
userWallet: UserWallet,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ xmlShimmer = "1.1.3"
|
|||
zxingQrCode = "3.5.1"
|
||||
mviCore = "1.3.1"
|
||||
kotlinSerialization = "1.4.1"
|
||||
arrow = "1.2.0"
|
||||
arrow = "1.2.3"
|
||||
reactiveNetwork = "3.0.8"
|
||||
walletConnectCore = "1.18.0"
|
||||
walletConnectWeb3 = "1.11.0"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue