From ddb8de496436b7eb8f4f55fc943777dfc3bfa3d2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Apr 2024 13:07:18 +0300 Subject: [PATCH 01/98] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 6 +- .../kotlin/com/tangem/domain/core/lce/Lce.kt | 96 ++++++++++++++++++ .../com/tangem/domain/core/lce/LceFlow.kt | 99 +++++++++++++++++++ .../com/tangem/domain/core/lce/LceRaise.kt | 86 ++++++++++++++++ .../com/tangem/domain/core/utils/EitherExt.kt | 37 +++++++ .../com/tangem/domain/core/utils/LceExt.kt | 75 ++++++++++++++ .../tokens/model/CryptoCurrencyStatus.kt | 20 ++-- .../domain/tokens/model/NetworkStatus.kt | 12 +-- .../domain/tokens/GetCardTokensListUseCase.kt | 22 ++--- .../domain/tokens/GetTokenListUseCase.kt | 14 +-- .../CurrenciesStatusesOperations.kt | 14 +-- .../operations/CurrencyStatusOperations.kt | 4 +- .../tokens/operations/TokenListOperations.kt | 11 --- .../domain/tokens/GetTokenListUseCaseTest.kt | 3 - .../swap/domain/di/SwapDomainModule.kt | 2 - .../TokenDetailsLoadedBalanceConverter.kt | 12 +-- .../SingleWalletCardStateConverter.kt | 6 +- .../SingleWalletMarketPriceConverter.kt | 8 +- .../WalletCurrencyActionsClickIntents.kt | 2 +- gradle/dependencies.toml | 2 +- 20 files changed, 447 insertions(+), 84 deletions(-) create mode 100644 domain/core/src/main/kotlin/com/tangem/domain/core/lce/Lce.kt create mode 100644 domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt create mode 100644 domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt create mode 100644 domain/core/src/main/kotlin/com/tangem/domain/core/utils/EitherExt.kt create mode 100644 domain/core/src/main/kotlin/com/tangem/domain/core/utils/LceExt.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 54089b880e..f0f6760cfe 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -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 diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/Lce.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/Lce.kt new file mode 100644 index 0000000000..65ef8788fa --- /dev/null +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/Lce.kt @@ -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 { + + /** + * 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(val partialContent: C?) : Lce() + + /** + * Represents the content state, which contains the loaded content. + * + * @param content The loaded content. + */ + data class Content(val content: C) : Lce() + + /** + * Represents the error state, which contains an error object. + * + * @param error The error that occurred during loading. + */ + data class Error(val error: E) : Lce() + + /** + * 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 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 map(ifContent: (C) -> T): Lce = 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 mapError(ifError: (E) -> T): Lce = 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 }, + ) +} \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt new file mode 100644 index 0000000000..3a5715abf5 --- /dev/null +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt @@ -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 = Flow> + +/** + * 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 @PublishedApi internal constructor( + private val raise: LceRaise, + private val scope: ProducerScope>, + private val ifLoading: LceRaise.(C) -> Lce, +) : Raise> 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): 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 lceFlow( + ifLoading: LceRaise.(C) -> Lce = { lceLoading(partialContent = it) }, + @BuilderInference block: suspend LceFlowScope.() -> Unit, +): LceFlow { + return channelFlow { + trySend(lceLoading()) + + lce { + val scope = LceFlowScope( + raise = this@lce, + scope = this@channelFlow, + ifLoading = ifLoading, + ) + + block(scope) + } + } +} \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt new file mode 100644 index 0000000000..22a94b519b --- /dev/null +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt @@ -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 @PublishedApi internal constructor( + private val raise: Raise>, +) : Raise> by raise { + + val isLoading: Atomic = 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 Lce.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 Lce.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 lce( + ifLoading: LceRaise.(C) -> Lce = { lceLoading(partialContent = it) }, + @BuilderInference block: LceRaise.() -> C, +): Lce = 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 }, +) \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/utils/EitherExt.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/utils/EitherExt.kt new file mode 100644 index 0000000000..457f3e4f5b --- /dev/null +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/utils/EitherExt.kt @@ -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 = Flow> + +/** + * 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 Either.toLce(isStillLoading: Boolean = false): Lce { + return when (this) { + is Either.Left -> Lce.Error(value) + is Either.Right -> { + if (isStillLoading) { + Lce.Loading(value) + } else { + Lce.Content(value) + } + } + } +} \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/utils/LceExt.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/utils/LceExt.kt new file mode 100644 index 0000000000..14f4d80e08 --- /dev/null +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/utils/LceExt.kt @@ -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 lceLoading(partialContent: C? = null): Lce = Lce.Loading(partialContent) + +/** + * Wraps the receiver object in a [Lce.Content] instance. + * + * @return A [Lce.Content] instance containing the receiver object. + */ +fun C.lceContent(): Lce = Lce.Content(content = this) + +/** + * Wraps the receiver object in a [Lce.Error] instance. + * + * @return A [Lce.Error] instance containing the receiver object. + */ +fun E.lceError(): Lce = 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 Lce.flatMap( + block: (content: C, isLoading: Boolean) -> Lce, +): Lce = 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 Lce.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 Lce.toEither(ifLoading: (maybeContent: C?) -> C): Either = fold( + ifLoading = { ifLoading(it).right() }, + ifContent = { it.right() }, + ifError = { it.left() }, +) \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt index 58eaf3cff6..6b098d5be3 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt @@ -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, 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, 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, override val networkAddress: NetworkAddress, - ) : Status(isError = false) + ) : Value(isError = false) } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkStatus.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkStatus.kt index 60fdf9e7b8..08a746b12e 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkStatus.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkStatus.kt @@ -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, val pendingTransactions: Map>, - ) : 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() } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCardTokensListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCardTokensListUseCase.kt index efe0126403..31b6ccc631 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCardTokensListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCardTokensListUseCase.kt @@ -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> { + operator fun invoke(userWalletId: UserWalletId): EitherFlow { return getTokensStatuses(userWalletId).transformLatest { maybeTokens -> maybeTokens.fold( ifLeft = { error -> @@ -37,9 +37,7 @@ class GetCardTokensListUseCase( } } - private fun getTokensStatuses( - userWalletId: UserWalletId, - ): Flow>> { + private fun getTokensStatuses(userWalletId: UserWalletId): EitherFlow> { val operations = CurrenciesStatusesOperations( userWalletId = userWalletId, currenciesRepository = currenciesRepository, @@ -56,7 +54,7 @@ class GetCardTokensListUseCase( private fun createTokenList( userWalletId: UserWalletId, tokens: List, - ): Flow> { + ): EitherFlow { val operations = TokenListOperations( userWalletId = userWalletId, tokens = tokens, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt index 1266f0cc23..a19f2c9cd2 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt @@ -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>> { 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 -> diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index fac9f7fc95..3e6005ebc7 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -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>> { + fun getCurrenciesStatusesFlow(): EitherFlow> { return getMultiCurrencyWalletCurrencies().transformLatest { maybeCurrencies -> val nonEmptyCurrencies = maybeCurrencies.fold( ifLeft = { error -> diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt index b02828d2c3..8284d1667e 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt @@ -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 diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt index 612140fbfb..9c75a10432 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt @@ -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, ) { - constructor( - userWalletId: UserWalletId, - tokens: List, - useCase: GetTokenListUseCase, - ) : this( - currenciesRepository = useCase.currenciesRepository, - userWalletId = userWalletId, - tokens = tokens, - ) - fun getTokenListFlow(): Flow> { return combine( getIsGrouped(), diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt index 68413e7f65..48b5575b45 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt @@ -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> = flowOf(MockTokenLists.isGrouped.right()), isSortedByBalance: Flow> = flowOf(MockTokenLists.isSortedByBalance.right()), ) = GetTokenListUseCase( - dispatchers = dispatchers, currenciesRepository = MockCurrenciesRepository( sortTokensResult = Unit.right(), removeCurrencyResult = Unit.right(), diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index 196d529e7b..f7ccce976e 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -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, ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 5048215ac6..05221db9ec 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -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( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletCardStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletCardStateConverter.kt index 028d2ea966..775957b478 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletCardStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletCardStateConverter.kt @@ -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 { @@ -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( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt index 157d0a1180..34579188d3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt @@ -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 { @@ -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) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 243f372827..761e754c7b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -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, ) { diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 42119bfeb6..79c3e47d06 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -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" From 128f3b8d4398545a139c563c723c9728a4b8adc4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 2 Apr 2024 11:14:57 +0800 Subject: [PATCH 02/98] Updated on 2026-08-14 --- .../com/tangem/tap/LockUserWalletsTimer.kt | 52 +++++++++++++------ .../main/java/com/tangem/tap/MainActivity.kt | 6 ++- .../local/preferences/PreferencesKeys.kt | 8 +++ .../settings/DefaultSettingsRepository.kt | 19 +++++++ .../preferences/PreferencesDataSource.kt | 14 ----- .../repositories/SettingsRepository.kt | 8 +++ 6 files changed, 75 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index 256bd62a5f..7dda361ec6 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction +import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.wallets.legacy.asLockable import com.tangem.tap.common.extensions.dispatchOnMain import kotlinx.coroutines.* @@ -13,6 +14,7 @@ import kotlin.time.Duration internal class LockUserWalletsTimer( owner: LifecycleOwner, + private val settingsRepository: SettingsRepository, private val duration: Duration = with(Duration) { 10.minutes }, ) : LifecycleOwner by owner, DefaultLifecycleObserver { @@ -28,24 +30,35 @@ internal class LockUserWalletsTimer( } override fun onResume(owner: LifecycleOwner) { - Timber.d( - """ + owner.lifecycleScope.launch { + val wasApplicationStopped = settingsRepository.wasApplicationStopped() + val shouldOpenWelcomeScreenOnResume = settingsRepository.shouldOpenWelcomeScreenOnResume() + + Timber.d( + """ Owner resumed - |- Was stopped: ${preferencesStorage.wasApplicationStopped} - |- Need to open welcome screen: ${preferencesStorage.shouldOpenWelcomeScreenOnResume} - """.trimIndent(), - ) - preferencesStorage.wasApplicationStopped = false - start() - if (preferencesStorage.shouldOpenWelcomeScreenOnResume) { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) - preferencesStorage.shouldOpenWelcomeScreenOnResume = false + |- Was stopped: $wasApplicationStopped + |- Need to open welcome screen: $shouldOpenWelcomeScreenOnResume + """.trimIndent(), + ) + + settingsRepository.setWasApplicationStopped(value = false) + + start() + + if (shouldOpenWelcomeScreenOnResume) { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) + settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false) + } } } override fun onStop(owner: LifecycleOwner) { Timber.d("Owner stopped") - preferencesStorage.wasApplicationStopped = true + + owner.lifecycleScope.launch { + settingsRepository.setWasApplicationStopped(value = true) + } } override fun onDestroy(owner: LifecycleOwner) { @@ -90,22 +103,27 @@ internal class LockUserWalletsTimer( private fun createDelayJob(): Job = lifecycleScope.launch(Dispatchers.Default) { val startTime = System.currentTimeMillis() + delay(duration) + if (isActive) { - val userWalletsListManager = userWalletsListManagerSafe?.asLockable() - ?: return@launch + val userWalletsListManager = userWalletsListManagerSafe?.asLockable() ?: return@launch + if (userWalletsListManager.hasUserWallets) { val currentTime = System.currentTimeMillis() + val wasApplicationStopped = settingsRepository.wasApplicationStopped() + Timber.d( """ Finished - |- App is stopped: ${preferencesStorage.wasApplicationStopped} + |- App is stopped: $wasApplicationStopped |- Millis passed: ${currentTime - startTime} """.trimIndent(), ) + userWalletsListManager.lock() - if (preferencesStorage.wasApplicationStopped) { - preferencesStorage.shouldOpenWelcomeScreenOnResume = true + if (wasApplicationStopped) { + settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true) } else { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) } diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index d5d7000089..7817df8a2f 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -35,6 +35,7 @@ import com.tangem.data.card.sdk.CardSdkLifecycleObserver import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles import com.tangem.domain.wallets.legacy.asLockable @@ -141,6 +142,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles + @Inject + lateinit var settingsRepository: SettingsRepository + internal val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow @@ -201,7 +205,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac tangemSdkManager = injectedTangemSdkManager appStateHolder.tangemSdkManager = tangemSdkManager backupService = BackupService.init(cardSdkConfigRepository.sdk, this) - lockUserWalletsTimer = LockUserWalletsTimer(owner = this) + lockUserWalletsTimer = LockUserWalletsTimer(owner = this, settingsRepository = settingsRepository) initIntentHandlers() diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index ab0ab7c350..d180755897 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -5,9 +5,11 @@ import com.tangem.datasource.local.preferences.PreferencesKeys.APP_LAUNCH_COUNT_ import com.tangem.datasource.local.preferences.PreferencesKeys.FUNDS_FOUND_DATE_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TANGEM_TOS_ACCEPTED_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.SAVE_USER_WALLETS_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_OPEN_WELCOME_ON_RESUME_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.SHOW_RATING_DIALOG_AT_LAUNCH_COUNT_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.USED_CARDS_INFO_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.USER_WAS_INTERACT_WITH_RATING_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.WAS_APPLICATION_STOPPED_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.WAS_TWINS_ONBOARDING_SHOWN /** @@ -65,6 +67,10 @@ object PreferencesKeys { val SEND_TAP_HELP_PREVIEW_KEY by lazy { booleanPreferencesKey(name = "sendTapHelpPreview") } + val WAS_APPLICATION_STOPPED_KEY by lazy { booleanPreferencesKey(name = "applicationStopped") } + + val SHOULD_OPEN_WELCOME_ON_RESUME_KEY by lazy { booleanPreferencesKey(name = "openWelcomeOnResume") } + fun getStart2CoinTOSAcceptedKey(region: String?) = booleanPreferencesKey(name = "start2Coin_tos_accepted_$region") } @@ -79,6 +85,8 @@ internal fun getTapPrefKeysToMigrate(): Set { USED_CARDS_INFO_KEY, WAS_TWINS_ONBOARDING_SHOWN, IS_TANGEM_TOS_ACCEPTED_KEY, + WAS_APPLICATION_STOPPED_KEY, + SHOULD_OPEN_WELCOME_ON_RESUME_KEY, ) .map(Preferences.Key<*>::name) .toSet() diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt index 71e8f8d654..b6c68d65f0 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt @@ -75,4 +75,23 @@ internal class DefaultSettingsRepository( value = isEnabled, ) } + + override suspend fun wasApplicationStopped(): Boolean { + return appPreferencesStore.getSyncOrDefault(key = PreferencesKeys.WAS_APPLICATION_STOPPED_KEY, default = false) + } + + override suspend fun setWasApplicationStopped(value: Boolean) { + appPreferencesStore.store(key = PreferencesKeys.WAS_APPLICATION_STOPPED_KEY, value = value) + } + + override suspend fun shouldOpenWelcomeScreenOnResume(): Boolean { + return appPreferencesStore.getSyncOrDefault( + key = PreferencesKeys.SHOULD_OPEN_WELCOME_ON_RESUME_KEY, + default = false, + ) + } + + override suspend fun setShouldOpenWelcomeScreenOnResume(value: Boolean) { + appPreferencesStore.store(key = PreferencesKeys.SHOULD_OPEN_WELCOME_ON_RESUME_KEY, value = value) + } } \ No newline at end of file diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt index e12d679fd7..64bde15385 100644 --- a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt +++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt @@ -29,18 +29,6 @@ class PreferencesDataSource @Inject internal constructor(applicationContext: Con putBoolean(SAVE_ACCESS_CODES_KEY, value) } - var wasApplicationStopped: Boolean - get() = preferences.getBoolean(APPLICATION_STOPPED_KEY, false) - set(value) = preferences.edit { - putBoolean(APPLICATION_STOPPED_KEY, value) - } - - var shouldOpenWelcomeScreenOnResume: Boolean - get() = preferences.getBoolean(OPEN_WELCOME_ON_RESUME_KEY, false) - set(value) = preferences.edit { - putBoolean(OPEN_WELCOME_ON_RESUME_KEY, value) - } - private fun incrementLaunchCounter() { var count = preferences.getInt(APP_LAUNCH_COUNT_KEY, 0) preferences.edit { putInt(APP_LAUNCH_COUNT_KEY, ++count) } @@ -51,7 +39,5 @@ class PreferencesDataSource @Inject internal constructor(applicationContext: Con private const val APP_LAUNCH_COUNT_KEY = "launchCount" private const val SAVE_WALLET_DIALOG_SHOWN_KEY = "saveUserWalletShown" private const val SAVE_ACCESS_CODES_KEY = "saveAccessCodes" - private const val APPLICATION_STOPPED_KEY = "applicationStopped" - private const val OPEN_WELCOME_ON_RESUME_KEY = "openWelcomeOnResume" } } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt index 63a3ab3247..06f89908f8 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt @@ -17,4 +17,12 @@ interface SettingsRepository { suspend fun isSendTapHelpPreviewEnabled(): Boolean suspend fun setSendTapHelpPreviewAvailability(isEnabled: Boolean) + + suspend fun wasApplicationStopped(): Boolean + + suspend fun setWasApplicationStopped(value: Boolean) + + suspend fun shouldOpenWelcomeScreenOnResume(): Boolean + + suspend fun setShouldOpenWelcomeScreenOnResume(value: Boolean) } \ No newline at end of file From 328c38c86be7ea4da0180955351ab6dce09e4bc5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 2 Apr 2024 11:37:11 +0800 Subject: [PATCH 03/98] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 5 ++++ .../AccessCodeRequestPolicyMiddleware.kt | 13 ++++++--- .../tangem/tap/di/domain/CardDomainModule.kt | 8 ------ .../details/redux/DetailsMiddleware.kt | 27 +++++++++++-------- .../features/details/redux/DetailsReducer.kt | 5 ++-- .../tap/features/home/redux/HomeMiddleware.kt | 5 ++-- .../saveWallet/redux/SaveWalletMiddleware.kt | 7 +++-- .../welcome/redux/WelcomeMiddleware.kt | 5 +++- .../tap/proxy/redux/DaggerGraphState.kt | 2 ++ .../local/preferences/PreferencesKeys.kt | 4 +++ .../card/DefaultCardSdkConfigRepository.kt | 9 +------ .../com/tangem/data/card/di/CardDataModule.kt | 11 ++------ .../settings/DefaultSettingsRepository.kt | 8 ++++++ .../preferences/PreferencesDataSource.kt | 7 ----- .../card/GetAccessCodeSavingStatusUseCase.kt | 15 ----------- .../repository/CardSdkConfigRepository.kt | 3 --- .../repositories/SettingsRepository.kt | 4 +++ 17 files changed, 66 insertions(+), 72 deletions(-) delete mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/GetAccessCodeSavingStatusUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 7c26fe7570..0ca1333ea3 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -37,6 +37,7 @@ import com.tangem.domain.common.LogConfig import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase +import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.walletmanager.WalletManagersFacade @@ -184,6 +185,9 @@ internal class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var tangemSdkLogger: TangemSdkLogger + + @Inject + lateinit var settingsRepository: SettingsRepository // endregion Injected override fun onCreate() { @@ -274,6 +278,7 @@ internal class TapApplication : Application(), ImageLoaderFactory { feedbackManagerFeatureToggles = feedbackManagerFeatureToggles, tangemSdkLogger = tangemSdkLogger, blockchainSDKLogger = blockchainSDKLogger, + settingsRepository = settingsRepository, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt index c8fa6db166..57be8c6e21 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt @@ -3,9 +3,10 @@ package com.tangem.tap.common.redux import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.preferencesStorage +import com.tangem.tap.mainScope import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store +import kotlinx.coroutines.launch import org.rekotlin.Middleware class AccessCodeRequestPolicyMiddleware { @@ -21,8 +22,12 @@ class AccessCodeRequestPolicyMiddleware { } private fun updateAccessCodeRequestPolicy(scanResponse: ScanResponse) { - store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( - isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes && scanResponse.card.isAccessCodeSet, - ) + mainScope.launch { + val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes() + + store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( + isBiometricsRequestPolicy = shouldSaveAccessCodes && scanResponse.card.isAccessCodeSet, + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index 084134986d..6d9b682e61 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -34,14 +34,6 @@ internal object CardDomainModule { return SetAccessCodeRequestPolicyUseCase(cardSdkConfigRepository = cardSdkConfigRepository) } - @Provides - @ViewModelScoped - fun provideGetAccessCodeSavingStatusUseCase( - cardSdkConfigRepository: CardSdkConfigRepository, - ): GetAccessCodeSavingStatusUseCase { - return GetAccessCodeSavingStatusUseCase(cardSdkConfigRepository = cardSdkConfigRepository) - } - @Provides @ViewModelScoped fun provideWasWalletAlreadySignedHashesConfirmedUseCase(cardRepository: CardRepository): WasCardScannedUseCase { diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 90aa10f2cd..26bd3c1710 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -330,8 +330,10 @@ class DetailsMiddleware { } private fun toggleSaveAccessCodes(state: DetailsState, enable: Boolean) = scope.launch { + val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes() + // Nothing to change - if (preferencesStorage.shouldSaveAccessCodes == enable) { + if (shouldSaveAccessCodes == enable) { store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) return@launch } @@ -465,12 +467,14 @@ class DetailsMiddleware { return CompletionResult.Success(Unit) } - private fun saveAccessCodes(scanResponse: ScanResponse?): CompletionResult { + private suspend fun saveAccessCodes(scanResponse: ScanResponse?): CompletionResult { Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.On)) - preferencesStorage.shouldSaveAccessCodes = true - store.inject(DaggerGraphState::cardSdkConfigRepository) - .setAccessCodeRequestPolicy(isBiometricsRequestPolicy = scanResponse?.card?.isAccessCodeSet == true) + store.inject(DaggerGraphState::settingsRepository).setShouldSaveAccessCodes(value = true) + + store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( + isBiometricsRequestPolicy = scanResponse?.card?.isAccessCodeSet == true, + ) return CompletionResult.Success(Unit) } @@ -480,9 +484,11 @@ class DetailsMiddleware { .doOnSuccess { Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.Off)) - preferencesStorage.shouldSaveAccessCodes = false - store.inject(DaggerGraphState::cardSdkConfigRepository) - .setAccessCodeRequestPolicy(isBiometricsRequestPolicy = false) + store.inject(DaggerGraphState::settingsRepository).setShouldSaveAccessCodes(value = false) + + store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( + isBiometricsRequestPolicy = false, + ) } .doOnFailure { error -> Timber.e(error, "Unable to delete saved access codes") @@ -575,9 +581,8 @@ class DetailsMiddleware { val prevUseBiometricsForAccessCode = cardSdkConfigRepository.isBiometricsRequestPolicy() // Update access code policy for access code saving when a card was scanned - cardSdkConfigRepository.setAccessCodeRequestPolicy( - isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes, - ) + val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes() + cardSdkConfigRepository.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = shouldSaveAccessCodes) store.inject(DaggerGraphState::scanCardProcessor).scan( analyticsEvent = Basic.CardWasScanned(CoreAnalyticsParam.ScannedFrom.MyWallets), diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 5b3f2fafd3..117b13438c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -7,7 +7,6 @@ import com.tangem.domain.models.scan.CardDTO import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.extensions.signedHashesCount -import com.tangem.tap.preferencesStorage import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -75,7 +74,9 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsSta appSettingsState = AppSettingsState( isBiometricsAvailable = tangemSdkManager.canUseBiometry, saveWallets = action.shouldSaveUserWallets, - saveAccessCodes = preferencesStorage.shouldSaveAccessCodes, + saveAccessCodes = runBlocking { + store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes() + }, selectedAppCurrency = store.state.globalState.appCurrency, selectedThemeMode = runBlocking { store.inject(DaggerGraphState::appThemeModeRepository).getAppThemeMode().firstOrNull() diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 18ec4fb565..5851a4f01c 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -19,7 +19,6 @@ import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL -import com.tangem.tap.preferencesStorage import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store @@ -77,8 +76,10 @@ private fun handleHomeAction(action: Action) { } private suspend fun readCard(analyticsEvent: AnalyticsEvent?) { + val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes() + store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( - isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes, + isBiometricsRequestPolicy = shouldSaveAccessCodes, ) store.inject(DaggerGraphState::scanCardProcessor).scan( diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 67272db72d..4bce913af4 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -156,7 +156,8 @@ internal class SaveWalletMiddleware { // Enable saving access codes only if this is the first time user save the wallet if (isFirstSavedWallet) { - preferencesStorage.shouldSaveAccessCodes = true + store.inject(DaggerGraphState::settingsRepository).setShouldSaveAccessCodes(value = true) + store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( isBiometricsRequestPolicy = userWallet.hasAccessCode, ) @@ -199,7 +200,9 @@ internal class SaveWalletMiddleware { private suspend fun handleSuccessAllowing(userWallet: UserWallet) { store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true) - preferencesStorage.shouldSaveAccessCodes = true + + store.inject(DaggerGraphState::settingsRepository).setShouldSaveAccessCodes(value = true) + store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( isBiometricsRequestPolicy = userWallet.hasAccessCode, ) diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index e2ff46f387..916d89105a 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -168,9 +168,12 @@ internal class WelcomeMiddleware { } private suspend inline fun scanCardInternal(crossinline onCardScanned: suspend (ScanResponse) -> Unit) { + val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes() + store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( - isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes, + isBiometricsRequestPolicy = shouldSaveAccessCodes, ) + store.inject(DaggerGraphState::scanCardProcessor).scan( analyticsEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.SignIn), onSuccess = { scanResponse -> diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index c23def20bc..76381f2a3c 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -15,6 +15,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase +import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.walletmanager.WalletManagersFacade @@ -71,4 +72,5 @@ data class DaggerGraphState( val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles? = null, val tangemSdkLogger: TangemSdkLogger? = null, val blockchainSDKLogger: BlockchainSDKLogger? = null, + val settingsRepository: SettingsRepository? = null, ) : StateType \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index d180755897..2c1f0d015d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -6,6 +6,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys.FUNDS_FOUND_DATE_ import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TANGEM_TOS_ACCEPTED_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.SAVE_USER_WALLETS_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_OPEN_WELCOME_ON_RESUME_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SAVE_ACCESS_CODES_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.SHOW_RATING_DIALOG_AT_LAUNCH_COUNT_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.USED_CARDS_INFO_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.USER_WAS_INTERACT_WITH_RATING_KEY @@ -71,6 +72,8 @@ object PreferencesKeys { val SHOULD_OPEN_WELCOME_ON_RESUME_KEY by lazy { booleanPreferencesKey(name = "openWelcomeOnResume") } + val SHOULD_SAVE_ACCESS_CODES_KEY by lazy { booleanPreferencesKey(name = "saveAccessCodes") } + fun getStart2CoinTOSAcceptedKey(region: String?) = booleanPreferencesKey(name = "start2Coin_tos_accepted_$region") } @@ -87,6 +90,7 @@ internal fun getTapPrefKeysToMigrate(): Set { IS_TANGEM_TOS_ACCEPTED_KEY, WAS_APPLICATION_STOPPED_KEY, SHOULD_OPEN_WELCOME_ON_RESUME_KEY, + SHOULD_SAVE_ACCESS_CODES_KEY, ) .map(Preferences.Key<*>::name) .toSet() diff --git a/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt b/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt index 0efec70af4..7e629bb338 100644 --- a/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt +++ b/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt @@ -6,7 +6,6 @@ import com.tangem.common.UserCodeType import com.tangem.common.core.CardIdDisplayFormat import com.tangem.common.core.UserCodeRequestPolicy import com.tangem.data.card.sdk.CardSdkProvider -import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.models.scan.ProductType @@ -14,14 +13,10 @@ import com.tangem.domain.models.scan.ProductType * Implementation of repository for managing of CardSDK config * * @property cardSdkProvider CardSDK instance provider - * @property preferencesDataSource application shared preferences * [REDACTED_AUTHOR] */ -internal class DefaultCardSdkConfigRepository( - private val cardSdkProvider: CardSdkProvider, - private val preferencesDataSource: PreferencesDataSource, -) : CardSdkConfigRepository { +internal class DefaultCardSdkConfigRepository(private val cardSdkProvider: CardSdkProvider) : CardSdkConfigRepository { @Deprecated("Use CardSdkConfigRepository's methods instead of this property") override val sdk: TangemSdk @@ -58,8 +53,6 @@ internal class DefaultCardSdkConfigRepository( } } - override fun isAccessCodeSavingEnabled(): Boolean = preferencesDataSource.shouldSaveAccessCodes - override fun getCommonSigner(cardId: String?) = CommonSigner( tangemSdk = sdk, cardId = cardId, diff --git a/data/card/src/main/java/com/tangem/data/card/di/CardDataModule.kt b/data/card/src/main/java/com/tangem/data/card/di/CardDataModule.kt index a526073cd2..eb53f5dea1 100644 --- a/data/card/src/main/java/com/tangem/data/card/di/CardDataModule.kt +++ b/data/card/src/main/java/com/tangem/data/card/di/CardDataModule.kt @@ -3,7 +3,6 @@ package com.tangem.data.card.di import com.tangem.data.card.DefaultCardRepository import com.tangem.data.card.DefaultCardSdkConfigRepository import com.tangem.data.card.sdk.CardSdkProvider -import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.card.repository.CardSdkConfigRepository @@ -19,14 +18,8 @@ internal object CardDataModule { @Provides @Singleton - fun provideCardSdkConfigRepository( - cardSdkProvider: CardSdkProvider, - preferencesDataSource: PreferencesDataSource, - ): CardSdkConfigRepository { - return DefaultCardSdkConfigRepository( - cardSdkProvider = cardSdkProvider, - preferencesDataSource = preferencesDataSource, - ) + fun provideCardSdkConfigRepository(cardSdkProvider: CardSdkProvider): CardSdkConfigRepository { + return DefaultCardSdkConfigRepository(cardSdkProvider = cardSdkProvider) } @Provides diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt index b6c68d65f0..c473fabe26 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt @@ -94,4 +94,12 @@ internal class DefaultSettingsRepository( override suspend fun setShouldOpenWelcomeScreenOnResume(value: Boolean) { appPreferencesStore.store(key = PreferencesKeys.SHOULD_OPEN_WELCOME_ON_RESUME_KEY, value = value) } + + override suspend fun shouldSaveAccessCodes(): Boolean { + return appPreferencesStore.getSyncOrDefault(key = PreferencesKeys.SHOULD_SAVE_ACCESS_CODES_KEY, default = false) + } + + override suspend fun setShouldSaveAccessCodes(value: Boolean) { + appPreferencesStore.store(key = PreferencesKeys.SHOULD_SAVE_ACCESS_CODES_KEY, value = value) + } } \ No newline at end of file diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt index 64bde15385..70206fb069 100644 --- a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt +++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt @@ -23,12 +23,6 @@ class PreferencesDataSource @Inject internal constructor(applicationContext: Con putBoolean(SAVE_WALLET_DIALOG_SHOWN_KEY, value) } - var shouldSaveAccessCodes: Boolean - get() = preferences.getBoolean(SAVE_ACCESS_CODES_KEY, false) - set(value) = preferences.edit { - putBoolean(SAVE_ACCESS_CODES_KEY, value) - } - private fun incrementLaunchCounter() { var count = preferences.getInt(APP_LAUNCH_COUNT_KEY, 0) preferences.edit { putInt(APP_LAUNCH_COUNT_KEY, ++count) } @@ -38,6 +32,5 @@ class PreferencesDataSource @Inject internal constructor(applicationContext: Con private const val PREFERENCES_NAME = "tapPrefs" private const val APP_LAUNCH_COUNT_KEY = "launchCount" private const val SAVE_WALLET_DIALOG_SHOWN_KEY = "saveUserWalletShown" - private const val SAVE_ACCESS_CODES_KEY = "saveAccessCodes" } } \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/GetAccessCodeSavingStatusUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/GetAccessCodeSavingStatusUseCase.kt deleted file mode 100644 index 538ac0f240..0000000000 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/GetAccessCodeSavingStatusUseCase.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.domain.card - -import com.tangem.domain.card.repository.CardSdkConfigRepository - -/** - * Use case for getting access code saving status - * - * @property cardSdkConfigRepository repository for managing of CardSDK config - * -[REDACTED_AUTHOR] - */ -class GetAccessCodeSavingStatusUseCase(private val cardSdkConfigRepository: CardSdkConfigRepository) { - - operator fun invoke(): Boolean = cardSdkConfigRepository.isAccessCodeSavingEnabled() -} \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt index 47d13a5a8c..98b2739bec 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt @@ -27,9 +27,6 @@ interface CardSdkConfigRepository { /** Update the card ID display format according to the [productType] of the scanned card */ fun updateCardIdDisplayFormat(productType: ProductType) - /** Check if access code saving is enabled */ - fun isAccessCodeSavingEnabled(): Boolean - /** Get common signer by [cardId] */ fun getCommonSigner(cardId: String?): CommonSigner diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt index 06f89908f8..3495a5fd4a 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt @@ -25,4 +25,8 @@ interface SettingsRepository { suspend fun shouldOpenWelcomeScreenOnResume(): Boolean suspend fun setShouldOpenWelcomeScreenOnResume(value: Boolean) + + suspend fun shouldSaveAccessCodes(): Boolean + + suspend fun setShouldSaveAccessCodes(value: Boolean) } \ No newline at end of file From a7fcb93df7efe20842de21316b0433ad12f1a11c Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 5 Apr 2024 15:46:53 +0800 Subject: [PATCH 04/98] Updated on 2026-08-14 --- .../com/tangem/tap/network/auth/DefaultAuthProvider.kt | 2 +- .../java/com/tangem/tap/network/auth/di/AuthModule.kt | 2 +- .../com/tangem/datasource/api/common}/AuthProvider.kt | 2 +- .../com/tangem/datasource/asset/AndroidAssetReader.kt | 5 ++--- .../com/tangem/datasource/di/AccountCreatorModule.kt | 2 +- .../datasource/local/blockchain/DefaultAccountCreator.kt | 2 +- .../java/com/tangem/datasource/utils/RequestHeader.kt | 9 ++------- .../com/tangem/domain/redux/global/DomainGlobalHub.kt | 2 +- .../feature/referral/data/ReferralRepositoryImpl.kt | 2 +- .../feature/referral/di/ReferralRepositoryModule.kt | 2 +- 10 files changed, 12 insertions(+), 18 deletions(-) rename {libs/auth/src/main/java/com/tangem/lib/auth => core/datasource/src/main/java/com/tangem/datasource/api/common}/AuthProvider.kt (82%) diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt index 27ac9a4336..16427253e8 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt @@ -1,7 +1,7 @@ package com.tangem.tap.network.auth import com.tangem.common.extensions.toHexString -import com.tangem.lib.auth.AuthProvider +import com.tangem.datasource.api.common.AuthProvider import com.tangem.tap.proxy.AppStateHolder internal class DefaultAuthProvider(private val appStateHolder: AppStateHolder) : AuthProvider { diff --git a/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt index 0185472a6e..06b2edda3a 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt @@ -1,9 +1,9 @@ package com.tangem.tap.network.auth.di +import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.lib.auth.AppVersionProvider -import com.tangem.lib.auth.AuthProvider import com.tangem.lib.auth.ExpressAuthProvider import com.tangem.tap.network.auth.DefaultAppVersionProvider import com.tangem.tap.network.auth.DefaultAuthProvider diff --git a/libs/auth/src/main/java/com/tangem/lib/auth/AuthProvider.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt similarity index 82% rename from libs/auth/src/main/java/com/tangem/lib/auth/AuthProvider.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt index 1323079ec6..a11b7bf574 100644 --- a/libs/auth/src/main/java/com/tangem/lib/auth/AuthProvider.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt @@ -1,4 +1,4 @@ -package com.tangem.lib.auth +package com.tangem.datasource.api.common /** * Provides auth for tangemTech API diff --git a/core/datasource/src/main/java/com/tangem/datasource/asset/AndroidAssetReader.kt b/core/datasource/src/main/java/com/tangem/datasource/asset/AndroidAssetReader.kt index ce19ff204e..18c7d0fea8 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/asset/AndroidAssetReader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/asset/AndroidAssetReader.kt @@ -21,8 +21,7 @@ internal class AndroidAssetReader @Inject constructor( .use(BufferedReader::readText) } - override fun openFile(fileName: String): InputStream { - return context.assets - .open(fileName) + override fun openFile(file: String): InputStream { + return context.assets.open(file) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/AccountCreatorModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/AccountCreatorModule.kt index 3b97ba24d9..580342503b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/AccountCreatorModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/AccountCreatorModule.kt @@ -1,9 +1,9 @@ package com.tangem.datasource.di import com.tangem.blockchain.common.AccountCreator +import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.local.blockchain.DefaultAccountCreator -import com.tangem.lib.auth.AuthProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultAccountCreator.kt b/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultAccountCreator.kt index 1501279d83..deaccc7ee9 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultAccountCreator.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultAccountCreator.kt @@ -5,10 +5,10 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.extensions.Result import com.tangem.common.extensions.toHexString +import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.CreateUserNetworkAccountBody -import com.tangem.lib.auth.AuthProvider internal class DefaultAccountCreator( private val authProvider: AuthProvider, diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt index bd725ea0b6..1ee0e4d9f9 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/RequestHeader.kt @@ -1,8 +1,7 @@ package com.tangem.datasource.utils +import com.tangem.datasource.api.common.AuthProvider import com.tangem.lib.auth.AppVersionProvider -import com.tangem.lib.auth.AuthBearerProvider -import com.tangem.lib.auth.AuthProvider import com.tangem.lib.auth.ExpressAuthProvider /** @@ -15,7 +14,7 @@ sealed class RequestHeader(vararg pairs: Pair String>) { /** Header list */ val values: List String>> = pairs.toList() - object CacheControlHeader : RequestHeader("Cache-Control" to { "max-age=600" }) + data object CacheControlHeader : RequestHeader("Cache-Control" to { "max-age=600" }) class AuthenticationHeader(authProvider: AuthProvider) : RequestHeader( "card_id" to { authProvider.getCardId() }, @@ -28,10 +27,6 @@ sealed class RequestHeader(vararg pairs: Pair String>) { "session-id" to { expressAuthProvider.getSessionId() }, ) - class AuthBearerHeader(authBearerProvider: AuthBearerProvider) : RequestHeader( - "Authorization" to { "Bearer " + authBearerProvider.getApiKey() }, - ) - class AppVersionPlatformHeaders(appVersionProvider: AppVersionProvider) : RequestHeader( "version" to { appVersionProvider.getAppVersion() }, "platform" to { "android" }, diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt index e552550257..18f081f8a7 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt @@ -2,11 +2,11 @@ package com.tangem.domain.redux.global import android.webkit.ValueCallback import com.tangem.common.extensions.toHexString +import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.utils.RequestHeader import com.tangem.domain.redux.BaseStoreHub import com.tangem.domain.redux.DomainState import com.tangem.domain.redux.ReStoreReducer -import com.tangem.lib.auth.AuthProvider import org.rekotlin.Action /** diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt index 2496fde6e8..93d2200f3c 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt @@ -3,6 +3,7 @@ package com.tangem.feature.referral.data import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.data.tokens.utils.CryptoCurrencyFactory +import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.StartReferralBody import com.tangem.datasource.demo.DemoModeDatasource @@ -15,7 +16,6 @@ import com.tangem.feature.referral.converters.ReferralConverter import com.tangem.feature.referral.domain.ReferralRepository import com.tangem.feature.referral.domain.models.ReferralData import com.tangem.feature.referral.domain.models.TokenData -import com.tangem.lib.auth.AuthProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext import javax.inject.Inject diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt index 2214c244dd..23164dbe82 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt @@ -1,12 +1,12 @@ package com.tangem.feature.referral.di +import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.demo.DemoModeDatasource import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.feature.referral.converters.ReferralConverter import com.tangem.feature.referral.data.ReferralRepositoryImpl import com.tangem.feature.referral.domain.ReferralRepository -import com.tangem.lib.auth.AuthProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides From 500ea8e6f38a7111d3a4c87c5c59758cedc7e954 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 5 Apr 2024 16:10:49 +0800 Subject: [PATCH 05/98] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + domain/legacy/build.gradle.kts | 1 + libs/blockchain-sdk/.gitignore | 1 + libs/blockchain-sdk/build.gradle.kts | 40 +++++++++ .../blockchainsdk/BlockchainSDKFactory.kt | 21 +++++ .../DefaultBlockchainSDKFactory.kt | 55 ++++++++++++ .../blockchainsdk/config/ConfigStorage.kt | 18 ++++ .../config/RuntimeConfigStorage.kt | 21 +++++ .../BlockchainSDKConfigConverter.kt | 83 +++++++++++++++++++ .../blockchainsdk/loader/ConfigLoader.kt | 14 ++++ .../blockchainsdk/loader/LocalConfigLoader.kt | 48 +++++++++++ settings.gradle.kts | 3 +- 12 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 libs/blockchain-sdk/.gitignore create mode 100644 libs/blockchain-sdk/build.gradle.kts create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/BlockchainSDKFactory.kt create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/ConfigStorage.kt create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/RuntimeConfigStorage.kt create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/ConfigLoader.kt create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3682efb9f5..0d459e7cb7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -80,6 +80,7 @@ dependencies { implementation(projects.core.deepLinks) implementation(projects.libs.crypto) implementation(projects.libs.auth) + implementation(projects.libs.blockchainSdk) implementation(projects.data.appCurrency) implementation(projects.data.appTheme) diff --git a/domain/legacy/build.gradle.kts b/domain/legacy/build.gradle.kts index bc0e716a7a..f21bb97aa5 100644 --- a/domain/legacy/build.gradle.kts +++ b/domain/legacy/build.gradle.kts @@ -13,6 +13,7 @@ dependencies { implementation(project(":core:utils")) implementation(project(":common")) implementation(project(":libs:auth")) + implementation(projects.libs.blockchainSdk) implementation(projects.domain.demo) implementation(projects.domain.models) implementation(projects.domain.tokens.models) diff --git a/libs/blockchain-sdk/.gitignore b/libs/blockchain-sdk/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/libs/blockchain-sdk/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/libs/blockchain-sdk/build.gradle.kts b/libs/blockchain-sdk/build.gradle.kts new file mode 100644 index 0000000000..3861d2b413 --- /dev/null +++ b/libs/blockchain-sdk/build.gradle.kts @@ -0,0 +1,40 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + id("configuration") +} + +android { + namespace = "com.tangem.libs.blockchain_sdk" +} + +dependencies { + + // region Core modules + implementation(projects.core.datasource) + implementation(projects.core.utils) + // endregion + + // region AndroidX libraries + implementation(deps.androidx.datastore) + // endregion + + // region DI libraries + implementation(deps.hilt.core) + kapt(deps.hilt.kapt) + // endregion + + // region Other libraries + implementation(deps.kotlin.coroutines) + implementation(deps.moshi) + implementation(deps.moshi.kotlin) + implementation(deps.timber) + // endregion + + // region Tangem libraries + implementation(deps.tangem.blockchain) { + exclude(module = "joda-time") + } + // endregion +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/BlockchainSDKFactory.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/BlockchainSDKFactory.kt new file mode 100644 index 0000000000..c38d226ee4 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/BlockchainSDKFactory.kt @@ -0,0 +1,21 @@ +package com.tangem.blockchainsdk + +import com.tangem.blockchain.common.WalletManagerFactory +import kotlinx.coroutines.flow.Flow + +/** + * Blockchain SDK components factory + * +[REDACTED_AUTHOR] + */ +interface BlockchainSDKFactory { + + /** Flow of [WalletManagerFactory] */ + val walletManagerFactory: Flow + + /** Initialize components */ + suspend fun init() + + /** Get [WalletManagerFactory] synchronously */ + suspend fun getWalletManagerFactorySync(): WalletManagerFactory? +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt new file mode 100644 index 0000000000..7b059dfa48 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt @@ -0,0 +1,55 @@ +package com.tangem.blockchainsdk + +import com.tangem.blockchain.common.AccountCreator +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.blockchain.common.datastorage.BlockchainDataStorage +import com.tangem.blockchain.common.logging.BlockchainSDKLogger +import com.tangem.blockchainsdk.config.ConfigStorage +import com.tangem.blockchainsdk.converters.BlockchainSDKConfigConverter +import com.tangem.blockchainsdk.loader.ConfigLoader +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map + +/** + * Implementation of Blockchain SDK components factory + * + * @property configLoader config loader + * @property configStorage config storage + * @property accountCreator account creator + * @property blockchainDataStorage blockchain data storage + * @property blockchainSDKLogger blockchain SDK logger + * +[REDACTED_AUTHOR] + */ +internal class DefaultBlockchainSDKFactory( + private val configLoader: ConfigLoader, + private val configStorage: ConfigStorage, + private val accountCreator: AccountCreator, + private val blockchainDataStorage: BlockchainDataStorage, + private val blockchainSDKLogger: BlockchainSDKLogger, +) : BlockchainSDKFactory { + + override val walletManagerFactory: Flow by lazy(::createWalletManagerFactory) + + override suspend fun init() { + val configValueModel = configLoader.load() ?: return + + configStorage.store( + config = BlockchainSDKConfigConverter.convert(value = configValueModel), + ) + } + + override suspend fun getWalletManagerFactorySync(): WalletManagerFactory? = walletManagerFactory.firstOrNull() + + private fun createWalletManagerFactory(): Flow { + return configStorage.get().map { config -> + WalletManagerFactory( + config = config, + accountCreator = accountCreator, + blockchainDataStorage = blockchainDataStorage, + loggers = listOf(blockchainSDKLogger), + ) + } + } +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/ConfigStorage.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/ConfigStorage.kt new file mode 100644 index 0000000000..e9aaaf807b --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/ConfigStorage.kt @@ -0,0 +1,18 @@ +package com.tangem.blockchainsdk.config + +import com.tangem.blockchain.common.BlockchainSdkConfig +import kotlinx.coroutines.flow.Flow + +/** + * Storage for [BlockchainSdkConfig] + * +[REDACTED_AUTHOR] + */ +internal interface ConfigStorage { + + /** Get flow of [BlockchainSdkConfig] */ + fun get(): Flow + + /** Store [BlockchainSdkConfig] */ + suspend fun store(config: BlockchainSdkConfig) +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/RuntimeConfigStorage.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/RuntimeConfigStorage.kt new file mode 100644 index 0000000000..0adb68868e --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/RuntimeConfigStorage.kt @@ -0,0 +1,21 @@ +package com.tangem.blockchainsdk.config + +import com.tangem.blockchain.common.BlockchainSdkConfig +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +/** + * Runtime storage for [BlockchainSdkConfig] + * +[REDACTED_AUTHOR] + */ +internal class RuntimeConfigStorage : ConfigStorage { + + private val configFlow = MutableStateFlow(value = BlockchainSdkConfig()) + + override fun get(): Flow = configFlow + + override suspend fun store(config: BlockchainSdkConfig) { + configFlow.value = config + } +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt new file mode 100644 index 0000000000..d7551a2eea --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt @@ -0,0 +1,83 @@ +package com.tangem.blockchainsdk.converters + +import com.tangem.blockchain.common.* +import com.tangem.datasource.config.models.ConfigValueModel +import com.tangem.utils.converter.Converter + +/** + * Converts [ConfigValueModel] to [BlockchainSdkConfig] + * +[REDACTED_AUTHOR] + */ +internal object BlockchainSDKConfigConverter : Converter { + + override fun convert(value: ConfigValueModel): BlockchainSdkConfig { + return BlockchainSdkConfig( + blockchairCredentials = BlockchairCredentials( + apiKey = value.blockchairApiKeys, + authToken = value.blockchairAuthorizationToken, + ), + blockcypherTokens = value.blockcypherTokens, + quickNodeSolanaCredentials = QuickNodeCredentials( + apiKey = value.quiknodeApiKey, + subdomain = value.quiknodeSubdomain, + ), + quickNodeBscCredentials = QuickNodeCredentials( + apiKey = value.bscQuiknodeApiKey, + subdomain = value.bscQuiknodeSubdomain, + ), + infuraProjectId = value.infuraProjectId, + tronGridApiKey = value.tronGridApiKey, + nowNodeCredentials = NowNodeCredentials(value.nowNodesApiKey), + getBlockCredentials = createGetBlockCredentials(value), + kaspaSecondaryApiUrl = value.kaspaSecondaryApiUrl, + tonCenterCredentials = TonCenterCredentials( + mainnetApiKey = value.tonCenterKeys.mainnet, + testnetApiKey = value.tonCenterKeys.testnet, + ), + chiaFireAcademyApiKey = value.chiaFireAcademyApiKey, + chiaTangemApiKey = value.chiaTangemApiKey, + ) + } + + private fun createGetBlockCredentials(configValues: ConfigValueModel): GetBlockCredentials? { + return configValues.getBlockAccessTokens?.let { accessTokens -> + GetBlockCredentials( + xrp = GetBlockAccessToken(jsonRpc = accessTokens.xrp?.jsonRPC), + cardano = GetBlockAccessToken(rosetta = accessTokens.cardano?.rosetta), + avalanche = GetBlockAccessToken(jsonRpc = accessTokens.avalanche?.jsonRPC), + eth = GetBlockAccessToken(jsonRpc = accessTokens.eth?.jsonRPC), + etc = GetBlockAccessToken(jsonRpc = accessTokens.etc?.jsonRPC), + fantom = GetBlockAccessToken(jsonRpc = accessTokens.fantom?.jsonRPC), + rsk = GetBlockAccessToken(jsonRpc = accessTokens.rsk?.jsonRPC), + bsc = GetBlockAccessToken(jsonRpc = accessTokens.bsc?.jsonRPC), + polygon = GetBlockAccessToken(jsonRpc = accessTokens.polygon?.jsonRPC), + gnosis = GetBlockAccessToken(jsonRpc = accessTokens.gnosis?.jsonRPC), + cronos = GetBlockAccessToken(jsonRpc = accessTokens.cronos?.jsonRPC), + solana = GetBlockAccessToken(jsonRpc = accessTokens.solana?.jsonRPC), + ton = GetBlockAccessToken(jsonRpc = accessTokens.ton?.jsonRPC), + tron = GetBlockAccessToken(rest = accessTokens.tron?.rest), + cosmos = GetBlockAccessToken(rest = accessTokens.cosmos?.rest), + near = GetBlockAccessToken(jsonRpc = accessTokens.near?.jsonRPC), + aptos = GetBlockAccessToken(rest = accessTokens.aptos?.rest), + dogecoin = GetBlockAccessToken( + jsonRpc = accessTokens.dogecoin?.jsonRPC, + blockBookRest = accessTokens.dogecoin?.blockBookRest, + ), + litecoin = GetBlockAccessToken( + jsonRpc = accessTokens.litecoin?.jsonRPC, + blockBookRest = accessTokens.litecoin?.blockBookRest, + ), + dash = GetBlockAccessToken( + jsonRpc = accessTokens.dash?.jsonRPC, + blockBookRest = accessTokens.dash?.blockBookRest, + ), + bitcoin = GetBlockAccessToken( + jsonRpc = accessTokens.bitcoin?.jsonRPC, + blockBookRest = accessTokens.bitcoin?.blockBookRest, + ), + algorand = GetBlockAccessToken(rest = accessTokens.algorand?.rest), + ) + } + } +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/ConfigLoader.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/ConfigLoader.kt new file mode 100644 index 0000000000..570013fd4a --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/ConfigLoader.kt @@ -0,0 +1,14 @@ +package com.tangem.blockchainsdk.loader + +import com.tangem.datasource.config.models.ConfigValueModel + +/** + * Config loader + * +[REDACTED_AUTHOR] + */ +internal interface ConfigLoader { + + /** Load config [ConfigValueModel] */ + suspend fun load(): ConfigValueModel? +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt new file mode 100644 index 0000000000..b516a909d3 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt @@ -0,0 +1,48 @@ +package com.tangem.blockchainsdk.loader + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.adapter +import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.config.models.ConfigValueModel +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runCatching +import timber.log.Timber + +/** + * Local config loader + * + * @param buildEnvironment build environment that defines config file name + * @param moshi moshi + * @property assetReader asset reader + * @property dispatchers dispatchers + */ +internal class LocalConfigLoader( + buildEnvironment: String, + moshi: Moshi, + private val assetReader: AssetReader, + private val dispatchers: CoroutineDispatcherProvider, +) : ConfigLoader { + + private val configFileName = "tangem-app-config/config_$buildEnvironment" + + @OptIn(ExperimentalStdlibApi::class) + private val adapter: JsonAdapter = moshi.adapter() + + override suspend fun load(): ConfigValueModel? { + return runCatching(dispatchers.io) { + val json = assetReader.readJson(fileName = configFileName) + adapter.fromJson(json) + } + .fold( + onSuccess = { parsedConfig -> + if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config is null")) + parsedConfig + }, + onFailure = { + Timber.e(it, "Failed to load config from assets") + null + }, + ) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 8c94ba65a8..f852c13383 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -75,8 +75,9 @@ include(":core:deep-links:global") // endregion Core modules // region Libs modules -include(":libs:crypto") include(":libs:auth") +include(":libs:blockchain-sdk") +include(":libs:crypto") include(":libs:visa") // endregion Libs modules From e2c8f8432154e68ca683b2fe21e927430a682bef Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 5 Apr 2024 17:27:54 +0800 Subject: [PATCH 06/98] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 12 ++-- .../di/domain/WalletManagersFacadeModule.kt | 20 ++----- .../com/tangem/tap/domain/TapWalletManager.kt | 21 ------- .../demo/DemoOnboardingNoteMiddleware.kt | 16 ++++-- .../note/redux/OnboardingNoteMiddleware.kt | 8 ++- .../twins/redux/TwinCardsMiddleware.kt | 7 ++- .../tap/proxy/redux/DaggerGraphState.kt | 4 +- .../DefaultWalletManagersFacade.kt | 20 +------ .../utils/WalletManagerFactory.kt | 24 ++------ .../di/BlockchainSDKFactoryModule.kt | 57 +++++++++++++++++++ 10 files changed, 99 insertions(+), 90 deletions(-) create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 0ca1333ea3..d3cdaa5e3e 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -12,9 +12,9 @@ import com.tangem.Log import com.tangem.LogFormat import com.tangem.TangemSdkLogger import com.tangem.blockchain.common.AccountCreator -import com.tangem.blockchain.common.datastorage.BlockchainDataStorage import com.tangem.blockchain.common.logging.BlockchainSDKLogger import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder +import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.filter.OneTimeEventFilter import com.tangem.core.featuretoggle.manager.FeatureTogglesManager @@ -156,9 +156,6 @@ internal class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var oneTimeEventFilter: OneTimeEventFilter - @Inject - lateinit var blockchainDataStorage: BlockchainDataStorage - @Inject lateinit var accountCreator: AccountCreator @@ -188,6 +185,9 @@ internal class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var settingsRepository: SettingsRepository + + @Inject + lateinit var blockchainSDKFactory: BlockchainSDKFactory // endregion Injected override fun onCreate() { @@ -223,6 +223,8 @@ internal class TapApplication : Application(), ImageLoaderFactory { } else { initUserWalletsListManager() } + + blockchainSDKFactory.init() } val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT) @@ -268,7 +270,6 @@ internal class TapApplication : Application(), ImageLoaderFactory { balanceHidingRepository = balanceHidingRepository, walletsRepository = walletsRepository, sendFeatureToggles = sendFeatureToggles, - blockchainDataStorage = blockchainDataStorage, accountCreator = accountCreator, userWalletsListManagerFeatureToggles = userWalletsListManagerFeatureToggles, generalUserWalletsListManager = generalUserWalletsListManager, @@ -279,6 +280,7 @@ internal class TapApplication : Application(), ImageLoaderFactory { tangemSdkLogger = tangemSdkLogger, blockchainSDKLogger = blockchainSDKLogger, settingsRepository = settingsRepository, + blockchainSDKFactory = blockchainSDKFactory, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt index 319ddd2c0e..e603c9c7f8 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt @@ -1,15 +1,11 @@ package com.tangem.tap.di.domain import com.squareup.moshi.Moshi -import com.tangem.blockchain.common.AccountCreator -import com.tangem.blockchain.common.datastorage.BlockchainDataStorage -import com.tangem.blockchain.common.logging.BlockchainSDKLogger +import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.datasource.asset.AssetReader -import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.di.SdkMoshi import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.walletmanager.WalletManagersStore -import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.walletmanager.DefaultWalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.feature.onboarding.data.MnemonicRepository @@ -28,26 +24,18 @@ internal object WalletManagersFacadeModule { fun provideWalletManagersFacade( walletManagersStore: WalletManagersStore, userWalletsStore: UserWalletsStore, - configManager: ConfigManager, - blockchainDataStorage: BlockchainDataStorage, - accountCreator: AccountCreator, - mnemonicRepository: MnemonicRepository, assetReader: AssetReader, @SdkMoshi moshi: Moshi, - blockchainSDKLogger: BlockchainSDKLogger, - feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles, + mnemonicRepository: MnemonicRepository, + blockchainSDKFactory: BlockchainSDKFactory, ): WalletManagersFacade { return DefaultWalletManagersFacade( walletManagersStore = walletManagersStore, userWalletsStore = userWalletsStore, - configManager = configManager, - blockchainDataStorage = blockchainDataStorage, assetReader = assetReader, moshi = moshi, mnemonic = mnemonicRepository.generateDefaultMnemonic(), - accountCreator = accountCreator, - blockchainSDKLogger = blockchainSDKLogger, - feedbackManagerFeatureToggles = feedbackManagerFeatureToggles, + blockchainSDKFactory = blockchainSDKFactory, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index f8a93381e8..7a799500ef 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -1,9 +1,7 @@ package com.tangem.tap.domain -import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet -import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.Basic import com.tangem.datasource.config.ConfigManager @@ -12,14 +10,12 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.models.UserWallet import com.tangem.operations.attestation.Attestation -import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.disclaimer.createDisclaimer import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction -import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider @@ -32,29 +28,12 @@ class TapWalletManager( private val dispatchers: CoroutineDispatcherProvider = AppCoroutineDispatcherProvider(), ) { - private val blockchainSdkConfig by lazy { - store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig() - } - private var loadUserWalletDataJob: Job? = null set(value) { field?.cancel() field = value } - val walletManagerFactory: WalletManagerFactory by lazy { - WalletManagerFactory( - 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() - }, - ) - } - suspend fun onWalletSelected(userWallet: UserWallet, sendAnalyticsEvent: Boolean) { // If a previous job was running, it gets cancelled before the new one starts, // ensuring that only one job is active at any given time. diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoOnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoOnboardingNoteMiddleware.kt index 4708050380..4b3ac9357d 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoOnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoOnboardingNoteMiddleware.kt @@ -6,11 +6,14 @@ import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.demo.DemoConfig import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.entities.ProgressState +import com.tangem.tap.common.extensions.inject import com.tangem.tap.domain.model.Currency import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.rekotlin.Action /** @@ -19,21 +22,23 @@ import org.rekotlin.Action internal class DemoOnboardingNoteMiddleware : DemoMiddleware { override fun tryHandle(config: DemoConfig, scanResponse: ScanResponse, action: Action): Boolean { - val globalState = store.state.globalState val noteState = store.state.onboardingNoteState - when (action) { + return when (action) { is OnboardingNoteAction.Balance.Update -> { val walletManager = if (noteState.walletManager != null) { noteState.walletManager } else { - val wmFactory = globalState.tapWalletManager.walletManagerFactory - val walletManager = wmFactory.makePrimaryWalletManager(scanResponse).guard { + val wmFactory = runBlocking { + store.inject(DaggerGraphState::blockchainSDKFactory).getWalletManagerFactorySync() + } + val walletManager = wmFactory?.makePrimaryWalletManager(scanResponse).guard { return false } store.dispatch(OnboardingNoteAction.SetWalletManager(walletManager)) walletManager } + val balanceAmount = config.getBalance(walletManager.wallet.blockchain) val loadedBalance = noteState.walletBalance.copy( value = balanceAmount.value!!, @@ -42,6 +47,7 @@ internal class DemoOnboardingNoteMiddleware : DemoMiddleware { error = null, criticalError = null, ) + walletManager.wallet.setAmount(balanceAmount) scope.launch { @@ -53,7 +59,7 @@ internal class DemoOnboardingNoteMiddleware : DemoMiddleware { } return true } + else -> false } - return false } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index ca78c542d3..ef5296a6b4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -23,12 +23,14 @@ import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.mainScope +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.rekotlin.Action import org.rekotlin.DispatchFunction import org.rekotlin.Middleware @@ -120,8 +122,10 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch val walletManager = if (noteState.walletManager != null) { noteState.walletManager } else { - val wmFactory = globalState.tapWalletManager.walletManagerFactory - val walletManager = wmFactory.makePrimaryWalletManager(scanResponse).guard { + val wmFactory = runBlocking { + store.inject(DaggerGraphState::blockchainSDKFactory).getWalletManagerFactorySync() + } + val walletManager = wmFactory?.makePrimaryWalletManager(scanResponse).guard { val message = "Loading cancelled. Cause: wallet manager didn't created" val customError = TapError.CustomError(message) store.dispatchErrorNotification(customError) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index e973dae678..dcb56e9eb5 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -36,6 +36,7 @@ import com.tangem.tap.userWalletsListManager import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.rekotlin.Action import org.rekotlin.DispatchFunction import org.rekotlin.Middleware @@ -244,8 +245,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) { val walletManager = if (twinCardsState.walletManager != null) { twinCardsState.walletManager } else { - val wmFactory = globalState.tapWalletManager.walletManagerFactory - val walletManager = wmFactory.makePrimaryWalletManager(getScanResponse()).guard { + val wmFactory = runBlocking { + store.inject(DaggerGraphState::blockchainSDKFactory).getWalletManagerFactorySync() + } + val walletManager = wmFactory?.makePrimaryWalletManager(getScanResponse()).guard { val message = "Loading cancelled. Cause: wallet manager didn't created" val customError = TapError.CustomError(message) store.dispatchErrorNotification(customError) diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 76381f2a3c..10d7489ef3 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -2,8 +2,8 @@ package com.tangem.tap.proxy.redux import com.tangem.TangemSdkLogger import com.tangem.blockchain.common.AccountCreator -import com.tangem.blockchain.common.datastorage.BlockchainDataStorage import com.tangem.blockchain.common.logging.BlockchainSDKLogger +import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.apptheme.repository.AppThemeModeRepository @@ -62,7 +62,6 @@ data class DaggerGraphState( val sendRouter: SendRouter? = null, val qrScanningRouter: QrScanningRouter? = null, val currenciesRepository: CurrenciesRepository? = null, - val blockchainDataStorage: BlockchainDataStorage? = null, val accountCreator: AccountCreator? = null, val userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles? = null, val generalUserWalletsListManager: UserWalletsListManager? = null, @@ -73,4 +72,5 @@ data class DaggerGraphState( val tangemSdkLogger: TangemSdkLogger? = null, val blockchainSDKLogger: BlockchainSDKLogger? = null, val settingsRepository: SettingsRepository? = null, + val blockchainSDKFactory: BlockchainSDKFactory? = null, ) : StateType \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 276758748b..d32086094a 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -10,23 +10,20 @@ import com.tangem.blockchain.common.* import com.tangem.blockchain.common.address.Address import com.tangem.blockchain.common.address.AddressType import com.tangem.blockchain.common.address.EstimationFeeAddressFactory -import com.tangem.blockchain.common.datastorage.BlockchainDataStorage -import com.tangem.blockchain.common.logging.BlockchainSDKLogger import com.tangem.blockchain.common.pagination.Page import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.txhistory.TransactionHistoryRequest import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult +import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.crypto.bip39.Mnemonic import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.datasource.asset.AssetReader -import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.domain.common.util.hasDerivation import com.tangem.domain.demo.DemoConfig -import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning @@ -52,23 +49,12 @@ class DefaultWalletManagersFacade( mnemonic: Mnemonic, assetReader: AssetReader, moshi: Moshi, - configManager: ConfigManager, - blockchainDataStorage: BlockchainDataStorage, - accountCreator: AccountCreator, - blockchainSDKLogger: BlockchainSDKLogger, - feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles, + blockchainSDKFactory: BlockchainSDKFactory, ) : WalletManagersFacade { private val demoConfig by lazy { DemoConfig() } private val resultFactory by lazy { UpdateWalletManagerResultFactory() } - private val walletManagerFactory by lazy { - WalletManagerFactory( - configManager = configManager, - accountCreator = accountCreator, - blockchainDataStorage = blockchainDataStorage, - blockchainSDKLogger = if (feedbackManagerFeatureToggles.isLocalLogsEnabled) blockchainSDKLogger else null, - ) - } + private val walletManagerFactory by lazy { WalletManagerFactory(blockchainSDKFactory) } private val sdkTokenConverter by lazy { SdkTokenConverter() } private val txHistoryStateConverter by lazy { SdkTransactionHistoryStateConverter() } private val txHistoryItemConverter by lazy { SdkTransactionHistoryItemConverter(assetReader, moshi) } diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/WalletManagerFactory.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/WalletManagerFactory.kt index 89a63b05f6..c5b825b82c 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/WalletManagerFactory.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/WalletManagerFactory.kt @@ -1,37 +1,21 @@ package com.tangem.domain.walletmanager.utils -import com.tangem.blockchain.common.AccountCreator import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationParams import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchain.common.datastorage.BlockchainDataStorage -import com.tangem.blockchain.common.logging.BlockchainSDKLogger +import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.datasource.config.ConfigManager import com.tangem.domain.common.DerivationStyleProvider import com.tangem.domain.common.extensions.makeWalletManagerForApp import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.models.scan.ScanResponse import timber.log.Timber -import com.tangem.blockchain.common.WalletManagerFactory as BlockchainWalletManagerFactory internal class WalletManagerFactory( - configManager: ConfigManager, - accountCreator: AccountCreator, - blockchainDataStorage: BlockchainDataStorage, - blockchainSDKLogger: BlockchainSDKLogger? = null, + private val blockchainSDKFactory: BlockchainSDKFactory, ) { - private val sdkWalletManagerFactory by lazy { - BlockchainWalletManagerFactory( - config = configManager.config.blockchainSdkConfig, - accountCreator = accountCreator, - blockchainDataStorage = blockchainDataStorage, - loggers = listOfNotNull(blockchainSDKLogger), - ) - } - - fun createWalletManager( + suspend fun createWalletManager( scanResponse: ScanResponse, blockchain: Blockchain, derivationPath: DerivationPath?, @@ -39,7 +23,7 @@ internal class WalletManagerFactory( val derivationParams = getDerivationParams(derivationPath, scanResponse.derivationStyleProvider) return try { - sdkWalletManagerFactory.makeWalletManagerForApp( + blockchainSDKFactory.getWalletManagerFactorySync()?.makeWalletManagerForApp( scanResponse = scanResponse, blockchain = blockchain, derivationParams = derivationParams, diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt new file mode 100644 index 0000000000..e3a6f2b62d --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt @@ -0,0 +1,57 @@ +package com.tangem.blockchainsdk.di + +import com.squareup.moshi.Moshi +import com.tangem.blockchain.common.AccountCreator +import com.tangem.blockchain.common.datastorage.BlockchainDataStorage +import com.tangem.blockchain.common.logging.BlockchainSDKLogger +import com.tangem.blockchainsdk.BlockchainSDKFactory +import com.tangem.blockchainsdk.DefaultBlockchainSDKFactory +import com.tangem.blockchainsdk.config.RuntimeConfigStorage +import com.tangem.blockchainsdk.loader.ConfigLoader +import com.tangem.blockchainsdk.loader.LocalConfigLoader +import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.di.SdkMoshi +import com.tangem.libs.blockchain_sdk.BuildConfig +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object BlockchainSDKFactoryModule { + + @Provides + @Singleton + fun provideBlockchainSDKFactory( + @SdkMoshi moshi: Moshi, + assetReader: AssetReader, + dispatchers: AppCoroutineDispatcherProvider, + accountCreator: AccountCreator, + blockchainDataStorage: BlockchainDataStorage, + blockchainSDKLogger: BlockchainSDKLogger, + ): BlockchainSDKFactory { + return DefaultBlockchainSDKFactory( + configLoader = createConfigLoader(moshi, assetReader, dispatchers), + configStorage = RuntimeConfigStorage(), + accountCreator = accountCreator, + blockchainDataStorage = blockchainDataStorage, + blockchainSDKLogger = blockchainSDKLogger, + ) + } + + private fun createConfigLoader( + moshi: Moshi, + assetReader: AssetReader, + dispatchers: AppCoroutineDispatcherProvider, + ): ConfigLoader { + return LocalConfigLoader( + buildEnvironment = BuildConfig.ENVIRONMENT, + moshi = moshi, + assetReader = assetReader, + dispatchers = dispatchers, + ) + } +} \ No newline at end of file From 8a4cf5155c4ec48fe42429c443c8d990b3880ac4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 5 Apr 2024 19:40:09 +0800 Subject: [PATCH 07/98] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 10 --------- .../tap/proxy/redux/DaggerGraphState.kt | 4 ---- .../datasource/di/AccountCreatorModule.kt | 22 ------------------- .../di/BlockchainDataStorageModule.kt | 21 ------------------ libs/blockchain-sdk/build.gradle.kts | 5 ++--- .../accountcreator}/DefaultAccountCreator.kt | 7 ++++-- .../DefaultBlockchainDataStorage.kt | 2 +- .../di/BlockchainSDKFactoryModule.kt | 16 +++++++++----- 8 files changed, 18 insertions(+), 69 deletions(-) delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/di/AccountCreatorModule.kt delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/di/BlockchainDataStorageModule.kt rename {core/datasource/src/main/java/com/tangem/datasource/local/blockchain => libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/accountcreator}/DefaultAccountCreator.kt (83%) rename {core/datasource/src/main/java/com/tangem/datasource/local/blockchain => libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/datastorage}/DefaultBlockchainDataStorage.kt (95%) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index d3cdaa5e3e..0200c6d57f 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -11,8 +11,6 @@ import com.orhanobut.logger.Logger import com.tangem.Log import com.tangem.LogFormat import com.tangem.TangemSdkLogger -import com.tangem.blockchain.common.AccountCreator -import com.tangem.blockchain.common.logging.BlockchainSDKLogger import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.core.analytics.Analytics @@ -156,9 +154,6 @@ internal class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var oneTimeEventFilter: OneTimeEventFilter - @Inject - lateinit var accountCreator: AccountCreator - @Inject lateinit var userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles @@ -177,9 +172,6 @@ internal class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles - @Inject - lateinit var blockchainSDKLogger: BlockchainSDKLogger - @Inject lateinit var tangemSdkLogger: TangemSdkLogger @@ -270,7 +262,6 @@ internal class TapApplication : Application(), ImageLoaderFactory { balanceHidingRepository = balanceHidingRepository, walletsRepository = walletsRepository, sendFeatureToggles = sendFeatureToggles, - accountCreator = accountCreator, userWalletsListManagerFeatureToggles = userWalletsListManagerFeatureToggles, generalUserWalletsListManager = generalUserWalletsListManager, wasTwinsOnboardingShownUseCase = wasTwinsOnboardingShownUseCase, @@ -278,7 +269,6 @@ internal class TapApplication : Application(), ImageLoaderFactory { cardRepository = cardRepository, feedbackManagerFeatureToggles = feedbackManagerFeatureToggles, tangemSdkLogger = tangemSdkLogger, - blockchainSDKLogger = blockchainSDKLogger, settingsRepository = settingsRepository, blockchainSDKFactory = blockchainSDKFactory, ), diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 10d7489ef3..ffbf62495b 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -1,8 +1,6 @@ package com.tangem.tap.proxy.redux import com.tangem.TangemSdkLogger -import com.tangem.blockchain.common.AccountCreator -import com.tangem.blockchain.common.logging.BlockchainSDKLogger import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.appcurrency.repository.AppCurrencyRepository @@ -62,7 +60,6 @@ data class DaggerGraphState( val sendRouter: SendRouter? = null, val qrScanningRouter: QrScanningRouter? = null, val currenciesRepository: CurrenciesRepository? = null, - val accountCreator: AccountCreator? = null, val userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles? = null, val generalUserWalletsListManager: UserWalletsListManager? = null, val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase? = null, @@ -70,7 +67,6 @@ data class DaggerGraphState( val cardRepository: CardRepository? = null, val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles? = null, val tangemSdkLogger: TangemSdkLogger? = null, - val blockchainSDKLogger: BlockchainSDKLogger? = null, val settingsRepository: SettingsRepository? = null, val blockchainSDKFactory: BlockchainSDKFactory? = null, ) : StateType \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/AccountCreatorModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/AccountCreatorModule.kt deleted file mode 100644 index 580342503b..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/di/AccountCreatorModule.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.datasource.di - -import com.tangem.blockchain.common.AccountCreator -import com.tangem.datasource.api.common.AuthProvider -import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.local.blockchain.DefaultAccountCreator -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object AccountCreatorModule { - - @Provides - @Singleton - fun provideAccountCreator(authProvider: AuthProvider, tangemTechApi: TangemTechApi): AccountCreator { - return DefaultAccountCreator(authProvider, tangemTechApi) - } -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/BlockchainDataStorageModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/BlockchainDataStorageModule.kt deleted file mode 100644 index 3d285b3fbe..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/di/BlockchainDataStorageModule.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.datasource.di - -import com.tangem.blockchain.common.datastorage.BlockchainDataStorage -import com.tangem.datasource.local.blockchain.DefaultBlockchainDataStorage -import com.tangem.datasource.local.preferences.AppPreferencesStore -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object BlockchainDataStorageModule { - - @Provides - @Singleton - fun provideBlockchainDataStorage(appPreferencesStore: AppPreferencesStore): BlockchainDataStorage { - return DefaultBlockchainDataStorage(appPreferencesStore) - } -} \ No newline at end of file diff --git a/libs/blockchain-sdk/build.gradle.kts b/libs/blockchain-sdk/build.gradle.kts index 3861d2b413..ecf2e9a795 100644 --- a/libs/blockchain-sdk/build.gradle.kts +++ b/libs/blockchain-sdk/build.gradle.kts @@ -33,8 +33,7 @@ dependencies { // endregion // region Tangem libraries - implementation(deps.tangem.blockchain) { - exclude(module = "joda-time") - } + implementation(deps.tangem.blockchain) { exclude(module = "joda-time") } + implementation(deps.tangem.card.core) // endregion } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultAccountCreator.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/accountcreator/DefaultAccountCreator.kt similarity index 83% rename from core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultAccountCreator.kt rename to libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/accountcreator/DefaultAccountCreator.kt index deaccc7ee9..be9daa7448 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultAccountCreator.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/accountcreator/DefaultAccountCreator.kt @@ -1,4 +1,4 @@ -package com.tangem.datasource.local.blockchain +package com.tangem.blockchainsdk.accountcreator import com.tangem.blockchain.common.AccountCreator import com.tangem.blockchain.common.Blockchain @@ -16,7 +16,10 @@ internal class DefaultAccountCreator( ) : AccountCreator { override suspend fun createAccount(blockchain: Blockchain, walletPublicKey: ByteArray): Result { - val request = CreateUserNetworkAccountBody(blockchain.id.removeSuffix("/test"), walletPublicKey.toHexString()) + val request = CreateUserNetworkAccountBody( + networkId = blockchain.id.removeSuffix("/test"), + walletPublicKey = walletPublicKey.toHexString(), + ) return try { val response = tangemTechApi.createUserNetworkAccount( cardPublicKey = authProvider.getCardPublicKey(), diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultBlockchainDataStorage.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/datastorage/DefaultBlockchainDataStorage.kt similarity index 95% rename from core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultBlockchainDataStorage.kt rename to libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/datastorage/DefaultBlockchainDataStorage.kt index 3f3d7192f5..85a3bab59f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/blockchain/DefaultBlockchainDataStorage.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/datastorage/DefaultBlockchainDataStorage.kt @@ -1,4 +1,4 @@ -package com.tangem.datasource.local.blockchain +package com.tangem.blockchainsdk.datastorage import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt index e3a6f2b62d..28c13b2f8b 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt @@ -1,16 +1,19 @@ package com.tangem.blockchainsdk.di import com.squareup.moshi.Moshi -import com.tangem.blockchain.common.AccountCreator -import com.tangem.blockchain.common.datastorage.BlockchainDataStorage import com.tangem.blockchain.common.logging.BlockchainSDKLogger import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.blockchainsdk.DefaultBlockchainSDKFactory +import com.tangem.blockchainsdk.accountcreator.DefaultAccountCreator import com.tangem.blockchainsdk.config.RuntimeConfigStorage +import com.tangem.blockchainsdk.datastorage.DefaultBlockchainDataStorage import com.tangem.blockchainsdk.loader.ConfigLoader import com.tangem.blockchainsdk.loader.LocalConfigLoader +import com.tangem.datasource.api.common.AuthProvider +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.asset.AssetReader import com.tangem.datasource.di.SdkMoshi +import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.libs.blockchain_sdk.BuildConfig import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import dagger.Module @@ -29,15 +32,16 @@ internal object BlockchainSDKFactoryModule { @SdkMoshi moshi: Moshi, assetReader: AssetReader, dispatchers: AppCoroutineDispatcherProvider, - accountCreator: AccountCreator, - blockchainDataStorage: BlockchainDataStorage, + authProvider: AuthProvider, + tangemTechApi: TangemTechApi, + appPreferencesStore: AppPreferencesStore, blockchainSDKLogger: BlockchainSDKLogger, ): BlockchainSDKFactory { return DefaultBlockchainSDKFactory( configLoader = createConfigLoader(moshi, assetReader, dispatchers), configStorage = RuntimeConfigStorage(), - accountCreator = accountCreator, - blockchainDataStorage = blockchainDataStorage, + accountCreator = DefaultAccountCreator(authProvider, tangemTechApi), + blockchainDataStorage = DefaultBlockchainDataStorage(appPreferencesStore), blockchainSDKLogger = blockchainSDKLogger, ) } From 55bda270362e7011d7f5bb4da442721672b400e0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 5 Apr 2024 21:40:49 +0800 Subject: [PATCH 08/98] Updated on 2026-08-14 --- .../tangem/tap/di/domain/SettingsDomainModule.kt | 8 ++++++++ .../features/details/redux/DetailsMiddleware.kt | 1 - .../tap/features/onboarding/OnboardingHelper.kt | 4 +++- .../features/saveWallet/redux/SaveWalletAction.kt | 2 -- .../saveWallet/redux/SaveWalletMiddleware.kt | 5 ----- .../saveWallet/redux/SaveWalletReducer.kt | 1 - .../features/saveWallet/ui/SaveWalletViewModel.kt | 12 +++++++++++- .../local/preferences/PreferencesKeys.kt | 4 ++++ .../data/settings/DefaultSettingsRepository.kt | 14 ++++++++------ .../tangem/data/settings/di/SettingsDataModule.kt | 14 ++------------ .../source/preferences/PreferencesDataSource.kt | 7 ------- .../settings/SetSaveWalletScreenShownUseCase.kt | 15 +++++++++++++++ .../settings/repositories/SettingsRepository.kt | 2 ++ 13 files changed, 53 insertions(+), 36 deletions(-) create mode 100644 domain/settings/src/main/java/com/tangem/domain/settings/SetSaveWalletScreenShownUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index 3c9fb7b2a9..6b230f3791 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -140,4 +140,12 @@ internal object SettingsDomainModule { fun provideNeverShowTapHelpUseCase(settingsRepository: SettingsRepository): NeverShowTapHelpUseCase { return NeverShowTapHelpUseCase(settingsRepository = settingsRepository) } + + @Provides + @ViewModelScoped + fun provideSetSaveWalletScreenShownUseCase( + settingsRepository: SettingsRepository, + ): SetSaveWalletScreenShownUseCase { + return SetSaveWalletScreenShownUseCase(settingsRepository = settingsRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 26bd3c1710..dd8119a13d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -404,7 +404,6 @@ class DetailsMiddleware { .doOnSuccess { Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On)) - preferencesStorage.shouldShowSaveUserWalletScreen = false store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true) } .doOnFailure { error -> diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 4030b48f6b..d51111b51a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -77,6 +77,8 @@ object OnboardingHelper { ) { Analytics.setContext(scanResponse) scope.launch { + val settingsRepository = store.inject(DaggerGraphState::settingsRepository) + when { // When should save user wallets, then save card without navigate to save wallet screen store.inject(DaggerGraphState::walletsRepository).shouldSaveUserWalletsSync() -> { @@ -97,7 +99,7 @@ object OnboardingHelper { } // When should not save user wallets but device has biometry and save wallet screen has not been shown, // then open save wallet screen - tangemSdkManager.canUseBiometry && preferencesStorage.shouldShowSaveUserWalletScreen -> { + tangemSdkManager.canUseBiometry && settingsRepository.shouldShowSaveUserWalletScreen() -> { proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError) delay(timeMillis = 1_200) diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt index ff18f45362..acee1bf884 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt @@ -26,7 +26,5 @@ internal sealed interface SaveWalletAction : Action { data object Cancel : SaveWalletAction } - data object SaveWalletWasShown : SaveWalletAction - data class SaveWalletAfterBackup(val hasBackupError: Boolean) : SaveWalletAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 4bce913af4..76cb51e2de 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -50,7 +50,6 @@ internal class SaveWalletMiddleware { is SaveWalletAction.Save -> saveWalletIfBiometricsEnrolled(state) is SaveWalletAction.AllowToUseBiometrics -> allowToUseBiometrics(state) is SaveWalletAction.EnrollBiometrics.Enroll -> enrollBiometrics() - is SaveWalletAction.SaveWalletWasShown -> saveWalletWasShown() is SaveWalletAction.Dismiss -> dismiss(state) is SaveWalletAction.SaveWalletAfterBackup -> saveWalletAfterBackup(state, action.hasBackupError) is SaveWalletAction.Save.Success, @@ -234,10 +233,6 @@ internal class SaveWalletMiddleware { } } - private fun saveWalletWasShown() { - preferencesStorage.shouldShowSaveUserWalletScreen = false - } - private suspend fun saveAccessCodeIfNeeded( accessCode: String?, cardsInWallet: Set, diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt index 88b5697eca..608f06326e 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt @@ -47,7 +47,6 @@ internal object SaveWalletReducer { needEnrollBiometrics = false, isSaveInProgress = false, ) - is SaveWalletAction.SaveWalletWasShown, is SaveWalletAction.SaveWalletAfterBackup, -> state } diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt index 99947493fc..17c05ac8f9 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt @@ -1,8 +1,10 @@ package com.tangem.tap.features.saveWallet.ui import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.domain.settings.SetSaveWalletScreenShownUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.tap.common.extensions.dispatchOnMain @@ -11,10 +13,12 @@ import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.features.saveWallet.redux.SaveWalletState import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog import com.tangem.tap.store +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import org.rekotlin.StoreSubscriber import javax.inject.Inject @@ -22,13 +26,19 @@ import javax.inject.Inject internal class SaveWalletViewModel @Inject constructor( private val userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles, private val analyticsEventHandler: AnalyticsEventHandler, + private val setSaveWalletScreenShownUseCase: SetSaveWalletScreenShownUseCase, + dispatchers: AppCoroutineDispatcherProvider, ) : ViewModel(), StoreSubscriber { + private val stateInternal = MutableStateFlow(SaveWalletScreenState()) val state: StateFlow = stateInternal init { subscribeToStoreChanges() - store.dispatchOnMain(SaveWalletAction.SaveWalletWasShown) + + viewModelScope.launch(dispatchers.main) { + setSaveWalletScreenShownUseCase() + } } fun saveWallet() { diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 2c1f0d015d..364a467ff3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -7,6 +7,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TANGEM_TOS_ACC import com.tangem.datasource.local.preferences.PreferencesKeys.SAVE_USER_WALLETS_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_OPEN_WELCOME_ON_RESUME_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SAVE_ACCESS_CODES_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_SAVE_USER_WALLET_SCREEN_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.SHOW_RATING_DIALOG_AT_LAUNCH_COUNT_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.USED_CARDS_INFO_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.USER_WAS_INTERACT_WITH_RATING_KEY @@ -22,6 +23,8 @@ object PreferencesKeys { val SAVE_USER_WALLETS_KEY by lazy { booleanPreferencesKey(name = "saveUserWallets") } + val SHOULD_SHOW_SAVE_USER_WALLET_SCREEN_KEY by lazy { booleanPreferencesKey("saveUserWalletShown") } + val APP_LAUNCH_COUNT_KEY by lazy { intPreferencesKey(name = "launchCount") } val SHOW_RATING_DIALOG_AT_LAUNCH_COUNT_KEY by lazy { intPreferencesKey(name = "showRatingDialogAtLaunchCount") } @@ -81,6 +84,7 @@ object PreferencesKeys { internal fun getTapPrefKeysToMigrate(): Set { return setOf( SAVE_USER_WALLETS_KEY, + SHOULD_SHOW_SAVE_USER_WALLET_SCREEN_KEY, APP_LAUNCH_COUNT_KEY, SHOW_RATING_DIALOG_AT_LAUNCH_COUNT_KEY, FUNDS_FOUND_DATE_KEY, diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt index c473fabe26..c4143615d7 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt @@ -1,23 +1,25 @@ package com.tangem.data.settings -import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.preferences.utils.store import com.tangem.domain.settings.repositories.SettingsRepository -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.withContext import org.joda.time.DateTime internal class DefaultSettingsRepository( - private val preferencesDataSource: PreferencesDataSource, private val appPreferencesStore: AppPreferencesStore, - private val dispatchers: CoroutineDispatcherProvider, ) : SettingsRepository { override suspend fun shouldShowSaveUserWalletScreen(): Boolean { - return withContext(dispatchers.io) { preferencesDataSource.shouldShowSaveUserWalletScreen } + return appPreferencesStore.getSyncOrDefault( + key = PreferencesKeys.SHOULD_SHOW_SAVE_USER_WALLET_SCREEN_KEY, + default = true, + ) + } + + override suspend fun setShouldShowSaveUserWalletScreen(value: Boolean) { + appPreferencesStore.store(key = PreferencesKeys.SHOULD_SHOW_SAVE_USER_WALLET_SCREEN_KEY, value = value) } override suspend fun isWalletScrollPreviewEnabled(): Boolean { diff --git a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt index 43d5796d15..6211aace4f 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt @@ -3,12 +3,10 @@ package com.tangem.data.settings.di import com.tangem.data.settings.DefaultAppRatingRepository import com.tangem.data.settings.DefaultSettingsRepository import com.tangem.data.settings.DefaultSwapPromoRepository -import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.settings.repositories.AppRatingRepository import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.repositories.SwapPromoRepository -import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -21,16 +19,8 @@ internal object SettingsDataModule { @Provides @Singleton - fun provideSettingsRepository( - preferencesDataSource: PreferencesDataSource, - appPreferencesStore: AppPreferencesStore, - dispatchers: CoroutineDispatcherProvider, - ): SettingsRepository { - return DefaultSettingsRepository( - preferencesDataSource = preferencesDataSource, - appPreferencesStore = appPreferencesStore, - dispatchers = dispatchers, - ) + fun provideSettingsRepository(appPreferencesStore: AppPreferencesStore): SettingsRepository { + return DefaultSettingsRepository(appPreferencesStore = appPreferencesStore) } @Provides diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt index 70206fb069..3499b0596c 100644 --- a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt +++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt @@ -17,12 +17,6 @@ class PreferencesDataSource @Inject internal constructor(applicationContext: Con incrementLaunchCounter() } - var shouldShowSaveUserWalletScreen: Boolean - get() = preferences.getBoolean(SAVE_WALLET_DIALOG_SHOWN_KEY, true) - set(value) = preferences.edit { - putBoolean(SAVE_WALLET_DIALOG_SHOWN_KEY, value) - } - private fun incrementLaunchCounter() { var count = preferences.getInt(APP_LAUNCH_COUNT_KEY, 0) preferences.edit { putInt(APP_LAUNCH_COUNT_KEY, ++count) } @@ -31,6 +25,5 @@ class PreferencesDataSource @Inject internal constructor(applicationContext: Con companion object { private const val PREFERENCES_NAME = "tapPrefs" private const val APP_LAUNCH_COUNT_KEY = "launchCount" - private const val SAVE_WALLET_DIALOG_SHOWN_KEY = "saveUserWalletShown" } } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/SetSaveWalletScreenShownUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/SetSaveWalletScreenShownUseCase.kt new file mode 100644 index 0000000000..593b82a683 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/SetSaveWalletScreenShownUseCase.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.settings + +import arrow.core.Either +import com.tangem.domain.settings.repositories.SettingsRepository + +class SetSaveWalletScreenShownUseCase( + private val settingsRepository: SettingsRepository, +) { + + suspend operator fun invoke(): Either { + return Either.catch { + settingsRepository.setShouldShowSaveUserWalletScreen(value = false) + } + } +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt index 3495a5fd4a..0c8cf8c462 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt @@ -4,6 +4,8 @@ interface SettingsRepository { suspend fun shouldShowSaveUserWalletScreen(): Boolean + suspend fun setShouldShowSaveUserWalletScreen(value: Boolean) + suspend fun isWalletScrollPreviewEnabled(): Boolean suspend fun setWalletScrollPreviewAvailability(isEnabled: Boolean) From 50a485e9869f294e0aacde39d4465d37cc2030f6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 7 Apr 2024 09:29:59 +0800 Subject: [PATCH 09/98] Updated on 2026-08-14 --- app/build.gradle.kts | 1 - .../java/com/tangem/tap/TapApplication.kt | 6 ---- .../tap/di/domain/SettingsDomainModule.kt | 8 +++++ .../tangem/tap/features/main/MainViewModel.kt | 4 +++ data/card/build.gradle.kts | 2 -- data/settings/build.gradle.kts | 2 -- .../settings/DefaultSettingsRepository.kt | 7 +++++ data/source/preferences/.gitignore | 1 - data/source/preferences/build.gradle.kts | 21 -------------- .../preferences/PreferencesDataSource.kt | 29 ------------------- .../preferences/adapters/BigDecimalAdapter.kt | 13 --------- .../preferences/di/PreferencesStoreModule.kt | 19 ------------ .../source/preferences/storage/Migration.kt | 5 ---- .../IncrementAppLaunchCounterUseCase.kt | 13 +++++++++ .../repositories/SettingsRepository.kt | 2 ++ settings.gradle.kts | 1 - 16 files changed, 34 insertions(+), 100 deletions(-) delete mode 100644 data/source/preferences/.gitignore delete mode 100644 data/source/preferences/build.gradle.kts delete mode 100644 data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt delete mode 100644 data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/adapters/BigDecimalAdapter.kt delete mode 100644 data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/di/PreferencesStoreModule.kt delete mode 100644 data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/Migration.kt create mode 100644 domain/settings/src/main/java/com/tangem/domain/settings/IncrementAppLaunchCounterUseCase.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0d459e7cb7..0e20e4fbdc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -88,7 +88,6 @@ dependencies { implementation(projects.data.card) implementation(projects.data.common) implementation(projects.data.settings) - implementation(projects.data.source.preferences) implementation(projects.data.tokens) implementation(projects.data.txhistory) implementation(projects.data.wallets) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 0200c6d57f..cdbf42b9a4 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -16,7 +16,6 @@ import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.filter.OneTimeEventFilter import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.datasource.api.common.MoshiConverter import com.tangem.datasource.api.common.createNetworkLoggingInterceptor import com.tangem.datasource.asset.AssetReader @@ -80,7 +79,6 @@ lateinit var store: Store lateinit var foregroundActivityObserver: ForegroundActivityObserver lateinit var activityResultCaller: ActivityResultCaller -lateinit var preferencesStorage: PreferencesDataSource lateinit var walletConnectRepository: WalletConnectRepository internal lateinit var derivationsFinder: DerivationsFinder @@ -106,9 +104,6 @@ internal class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var customTokenFeatureToggles: CustomTokenFeatureToggles - @Inject - lateinit var preferencesDataSource: PreferencesDataSource - @Inject lateinit var walletConnect2Repository: WalletConnect2Repository @@ -202,7 +197,6 @@ internal class TapApplication : Application(), ImageLoaderFactory { activityResultCaller = foregroundActivityObserver registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks) - preferencesStorage = preferencesDataSource walletConnectRepository = WalletConnectRepository(this) // TODO: Try to performance and user experience. diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index 6b230f3791..b6f96dfe1a 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -148,4 +148,12 @@ internal object SettingsDomainModule { ): SetSaveWalletScreenShownUseCase { return SetSaveWalletScreenShownUseCase(settingsRepository = settingsRepository) } + + @Provides + @ViewModelScoped + fun provideIncrementAppLaunchCounterUseCase( + settingsRepository: SettingsRepository, + ): IncrementAppLaunchCounterUseCase { + return IncrementAppLaunchCounterUseCase(settingsRepository = settingsRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 6f5d289dd9..1f09d65d4a 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -11,6 +11,7 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.ListenToFlipsUseCase import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase +import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase import com.tangem.tap.features.main.model.MainScreenState import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel @@ -26,6 +27,7 @@ internal class MainViewModel @Inject constructor( private val reduxNavController: ReduxNavController, private val fetchAppCurrenciesUseCase: FetchAppCurrenciesUseCase, private val deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase, + private val incrementAppLaunchCounterUseCase: IncrementAppLaunchCounterUseCase, private val dispatchers: CoroutineDispatcherProvider, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel(), MainIntents { @@ -40,6 +42,8 @@ internal class MainViewModel @Inject constructor( val state: StateFlow = stateHolder.stateFlow init { + viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() } + updateAppCurrencies() observeFlips() displayBalancesHidingStatusToast() diff --git a/data/card/build.gradle.kts b/data/card/build.gradle.kts index 872f5ce751..eb8abfb89e 100644 --- a/data/card/build.gradle.kts +++ b/data/card/build.gradle.kts @@ -26,8 +26,6 @@ dependencies { implementation(projects.core.datasource) implementation(projects.core.utils) - implementation(projects.data.source.preferences) - implementation(projects.domain.card) implementation(projects.domain.models) } \ No newline at end of file diff --git a/data/settings/build.gradle.kts b/data/settings/build.gradle.kts index f12116b4cf..96f4640242 100644 --- a/data/settings/build.gradle.kts +++ b/data/settings/build.gradle.kts @@ -15,8 +15,6 @@ dependencies { implementation(projects.core.datasource) implementation(projects.core.utils) - implementation(projects.data.source.preferences) - implementation(projects.domain.balanceHiding.models) implementation(projects.domain.settings) diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt index c4143615d7..335d4b86fd 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt @@ -104,4 +104,11 @@ internal class DefaultSettingsRepository( override suspend fun setShouldSaveAccessCodes(value: Boolean) { appPreferencesStore.store(key = PreferencesKeys.SHOULD_SAVE_ACCESS_CODES_KEY, value = value) } + + override suspend fun incrementAppLaunchCounter() { + appPreferencesStore.editData { preferences -> + val count = preferences.getOrDefault(key = PreferencesKeys.APP_LAUNCH_COUNT_KEY, default = 0) + preferences[PreferencesKeys.APP_LAUNCH_COUNT_KEY] = count + 1 + } + } } \ No newline at end of file diff --git a/data/source/preferences/.gitignore b/data/source/preferences/.gitignore deleted file mode 100644 index 796b96d1c4..0000000000 --- a/data/source/preferences/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/data/source/preferences/build.gradle.kts b/data/source/preferences/build.gradle.kts deleted file mode 100644 index 3768a33a8c..0000000000 --- a/data/source/preferences/build.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - alias(deps.plugins.kotlin.android) - alias(deps.plugins.kotlin.kapt) - alias(deps.plugins.android.library) - id("configuration") -} - -android { - namespace = "com.tangem.data.source.preferences" -} - -dependencies { - implementation(deps.androidx.core.ktx) - implementation(deps.moshi) - implementation(deps.moshi.kotlin) - implementation(deps.hilt.android) - kapt(deps.hilt.kapt) - - // For MoshiJsonConverter - implementation(deps.tangem.card.core) -} \ No newline at end of file diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt deleted file mode 100644 index 3499b0596c..0000000000 --- a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.tangem.data.source.preferences - -import android.content.Context -import android.content.SharedPreferences -import androidx.core.content.edit -import javax.inject.Inject - -// 🔥FIXME: Only logic to work with preferences must be here, must be separated to repositories -// TODO: Replace shared preferences with DataStore -@Deprecated("Create repository instead") -class PreferencesDataSource @Inject internal constructor(applicationContext: Context) { - - private val preferences: SharedPreferences = - applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) - - init { - incrementLaunchCounter() - } - - private fun incrementLaunchCounter() { - var count = preferences.getInt(APP_LAUNCH_COUNT_KEY, 0) - preferences.edit { putInt(APP_LAUNCH_COUNT_KEY, ++count) } - } - - companion object { - private const val PREFERENCES_NAME = "tapPrefs" - private const val APP_LAUNCH_COUNT_KEY = "launchCount" - } -} \ No newline at end of file diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/adapters/BigDecimalAdapter.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/adapters/BigDecimalAdapter.kt deleted file mode 100644 index d2adc38e1d..0000000000 --- a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/adapters/BigDecimalAdapter.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.data.source.preferences.adapters - -import com.squareup.moshi.FromJson -import com.squareup.moshi.ToJson -import java.math.BigDecimal - -class BigDecimalAdapter { - @FromJson - fun fromJson(value: String) = BigDecimal(value) - - @ToJson - fun toJson(value: BigDecimal) = value.toString() -} \ No newline at end of file diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/di/PreferencesStoreModule.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/di/PreferencesStoreModule.kt deleted file mode 100644 index 5961efde84..0000000000 --- a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/di/PreferencesStoreModule.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.data.source.preferences.di - -import android.content.Context -import com.tangem.data.source.preferences.PreferencesDataSource -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object PreferencesStoreModule { - - @Provides - @Singleton - fun providePreferencesStore(@ApplicationContext context: Context) = PreferencesDataSource(context) -} \ No newline at end of file diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/Migration.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/Migration.kt deleted file mode 100644 index e1cc25e4ba..0000000000 --- a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/Migration.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.data.source.preferences.storage - -internal interface Migration { - fun migrate() -} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/IncrementAppLaunchCounterUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/IncrementAppLaunchCounterUseCase.kt new file mode 100644 index 0000000000..c429dc2c66 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/IncrementAppLaunchCounterUseCase.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.settings + +import arrow.core.Either +import com.tangem.domain.settings.repositories.SettingsRepository + +class IncrementAppLaunchCounterUseCase( + private val settingsRepository: SettingsRepository, +) { + + suspend operator fun invoke(): Either { + return Either.catch { settingsRepository.incrementAppLaunchCounter() } + } +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt index 0c8cf8c462..3b5f6d3c40 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt @@ -31,4 +31,6 @@ interface SettingsRepository { suspend fun shouldSaveAccessCodes(): Boolean suspend fun setShouldSaveAccessCodes(value: Boolean) + + suspend fun incrementAppLaunchCounter() } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f852c13383..f276efc474 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -151,7 +151,6 @@ include(":data:balance-hiding") include(":data:common") include(":data:card") include(":data:tokens") -include(":data:source:preferences") include(":data:settings") include(":data:txhistory") include(":data:wallets") From afb2536da1cbd2718bf5836479071a2495f14772 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 5 Apr 2024 15:32:12 +0100 Subject: [PATCH 10/98] Updated on 2026-08-14 --- app/src/main/assets/testnet_tokens.json | 10 +++++++++ .../core/ui/extensions/BlockchainIcons.kt | 5 +++++ core/ui/src/main/res/drawable/ic_base_22.xml | 18 +++++++++++++++ core/ui/src/main/res/drawable/img_base_22.xml | 16 ++++++++++++++ .../QuotesUnsupportedCurrenciesIdAdapter.kt | 1 + .../domain/common/extensions/Blockchain.kt | 5 +++++ .../DefaultWalletManagersFacade.kt | 22 +++++++++++++++++-- gradle/dependencies.toml | 2 +- 8 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_base_22.xml create mode 100644 core/ui/src/main/res/drawable/img_base_22.xml diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json index 423202eeda..e10692c072 100644 --- a/app/src/main/assets/testnet_tokens.json +++ b/app/src/main/assets/testnet_tokens.json @@ -678,6 +678,16 @@ "networkId": "polygon-zkevm/test" } ] + }, + { + "id": "base-ethereum", + "name": "Base", + "symbol": "ETH", + "networks": [ + { + "networkId": "base/test" + } + ] } ] } diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index 0a77e69e7e..1a95df0142 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -66,6 +66,7 @@ fun getActiveIconRes(blockchainId: String): Int { "flare", "flare/test" -> R.drawable.img_flare_22 "taraxa", "taraxa/test" -> R.drawable.img_taraxa_22 "radiant" -> R.drawable.img_radiant_22 + "base" -> R.drawable.img_base_22 else -> R.drawable.ic_alert_24 } } @@ -133,6 +134,7 @@ fun getActiveIconResByNetworkId(networkId: String): Int { "flare-network", "flare-network/test" -> R.drawable.img_flare_22 "taraxa", "taraxa/test" -> R.drawable.img_taraxa_22 "radiant" -> R.drawable.img_radiant_22 + "base" -> R.drawable.img_base_22 else -> R.drawable.ic_alert_24 } } @@ -197,6 +199,7 @@ fun getActiveIconResByCoinId(coinId: String): Int { "flare-networks" -> R.drawable.img_flare_22 "taraxa" -> R.drawable.img_taraxa_22 "radiant" -> R.drawable.img_radiant_22 + "base" -> R.drawable.img_base_22 else -> R.drawable.ic_alert_24 } } @@ -264,6 +267,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int { "flare", "flare/test" -> R.drawable.ic_flare_22 "taraxa", "taraxa/test" -> R.drawable.ic_taraxa_22 "radiant" -> R.drawable.ic_radiant_22 + "base", "base/test" -> R.drawable.ic_base_22 else -> R.drawable.ic_alert_24 } } @@ -331,6 +335,7 @@ fun getGreyedOutIconResByNetworkId(networkId: String): Int { "flare-network", "flare-network/test" -> R.drawable.ic_flare_22 "taraxa", "taraxa/test" -> R.drawable.ic_taraxa_22 "radiant" -> R.drawable.ic_radiant_22 + "base", "base/test" -> R.drawable.ic_base_22 else -> R.drawable.ic_alert_24 } } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_base_22.xml b/core/ui/src/main/res/drawable/ic_base_22.xml new file mode 100644 index 0000000000..7c135649e4 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_base_22.xml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/core/ui/src/main/res/drawable/img_base_22.xml b/core/ui/src/main/res/drawable/img_base_22.xml new file mode 100644 index 0000000000..a426d0c8b2 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_base_22.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt index 6b2779299c..0e69a31330 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt @@ -55,6 +55,7 @@ internal class QuotesUnsupportedCurrenciesIdAdapter { "manta-network-ethereum" to "ethereum", "polygon-zkevm-ethereum" to "ethereum", "aurora-ethereum" to "ethereum", + "base-ethereum" to "ethereum", ) } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index e3ed6aabed..d3a65d7373 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -112,6 +112,8 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "flare-network/test" -> Blockchain.FlareTestnet "taraxa" -> Blockchain.Taraxa "taraxa/test" -> Blockchain.TaraxaTestnet + "base" -> Blockchain.Base + "base/test" -> Blockchain.BaseTestnet else -> null } } @@ -225,6 +227,8 @@ fun Blockchain.toNetworkId(): String { Blockchain.FlareTestnet -> "flare-network/test" Blockchain.Taraxa -> "taraxa" Blockchain.TaraxaTestnet -> "taraxa/test" + Blockchain.Base -> "base" + Blockchain.BaseTestnet -> "base/test" } } @@ -297,6 +301,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Mantle, Blockchain.MantleTestnet -> "mantle" Blockchain.Flare, Blockchain.FlareTestnet -> "flare-networks" Blockchain.Taraxa, Blockchain.TaraxaTestnet -> "taraxa" + Blockchain.Base, Blockchain.BaseTestnet -> "base" } } diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index d32086094a..42562a2735 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -178,7 +178,16 @@ class DefaultWalletManagersFacade( address = walletManager.wallet.address, filterType = when (currency) { is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin - is CryptoCurrency.Token -> TransactionHistoryRequest.FilterType.Contract(currency.contractAddress) + is CryptoCurrency.Token -> { + val blockchainToken = Token( + name = currency.name, + symbol = currency.symbol, + contractAddress = currency.contractAddress, + decimals = currency.decimals, + id = currency.id.rawCurrencyId, + ) + TransactionHistoryRequest.FilterType.Contract(blockchainToken) + } }, ) .let(txHistoryStateConverter::convert) @@ -207,7 +216,16 @@ class DefaultWalletManagersFacade( pageSize = pageSize, filterType = when (currency) { is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin - is CryptoCurrency.Token -> TransactionHistoryRequest.FilterType.Contract(currency.contractAddress) + is CryptoCurrency.Token -> { + val blockchainToken = Token( + name = currency.name, + symbol = currency.symbol, + contractAddress = currency.contractAddress, + decimals = currency.decimals, + id = currency.id.rawCurrencyId, + ) + TransactionHistoryRequest.FilterType.Contract(blockchainToken) + } }, ), ) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 79c3e47d06..5e7031d48b 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-547" +tangemBlockchainSdk = "develop-576" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-339" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 423e1259748ed75ace289df0e16884aba9fb1233 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 6 Apr 2024 16:48:02 +0800 Subject: [PATCH 11/98] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 28 ++--- .../java/com/tangem/tap/TapApplication.kt | 23 +--- .../tap/common/redux/global/GlobalReducer.kt | 13 +-- ...ultUserWalletsListManagerFeatureToggles.kt | 12 -- ...rWalletsListManagerFeatureTogglesModule.kt | 23 ---- .../di/UserWalletsListManagerModule.kt | 2 +- .../di/UserWalletsListManagerProvider.kt | 86 -------------- .../details/redux/DetailsMiddleware.kt | 91 --------------- .../features/onboarding/OnboardingHelper.kt | 7 +- .../redux/OnboardingWalletMiddleware.kt | 1 - .../saveWallet/redux/SaveWalletAction.kt | 4 +- .../saveWallet/redux/SaveWalletMiddleware.kt | 106 +----------------- .../saveWallet/redux/SaveWalletReducer.kt | 5 +- .../saveWallet/ui/SaveWalletViewModel.kt | 9 +- .../tap/proxy/redux/DaggerGraphState.kt | 2 - .../configs/feature_toggles_config.json | 4 - .../UserWalletsListManagerFeatureToggles.kt | 6 - .../wallets/legacy/WalletsStateHolder.kt | 2 + 18 files changed, 28 insertions(+), 396 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/DefaultUserWalletsListManagerFeatureToggles.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerFeatureTogglesModule.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt delete mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerFeatureToggles.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 7817df8a2f..ff642f28b1 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -37,7 +37,6 @@ import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles import com.tangem.domain.wallets.legacy.asLockable import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.features.managetokens.navigation.ManageTokensUi @@ -54,7 +53,6 @@ import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder import com.tangem.tap.common.redux.NotificationsHandler import com.tangem.tap.domain.TangemSdkManager -import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor import com.tangem.tap.features.intentHandler.IntentProcessor import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler @@ -90,11 +88,13 @@ private val mainCoroutineContext: CoroutineContext get() = Job() + Dispatchers.Main + FeatureCoroutineExceptionHandler.create("mainScope") val mainScope = CoroutineScope(mainCoroutineContext) -// TODO: Move to DI -val userWalletsListManagerSafe: UserWalletsListManager? - get() = store.state.globalState.userWalletsListManager -val userWalletsListManager: UserWalletsListManager - get() = userWalletsListManagerSafe!! +// TODO: will be remove in this task [REDACTED_JIRA] +@Deprecated(message = "Provide UserWalletsListManager using DI") +val userWalletsListManagerSafe: UserWalletsListManager? get() = store.state.globalState.userWalletsListManager + +// TODO: will be remove in this task [REDACTED_JIRA] +@Deprecated(message = "Provide UserWalletsListManager using DI") +val userWalletsListManager: UserWalletsListManager get() = userWalletsListManagerSafe!! @AndroidEntryPoint class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder { @@ -139,9 +139,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var deepLinksRegistry: DeepLinksRegistry - @Inject - lateinit var userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles - @Inject lateinit var settingsRepository: SettingsRepository @@ -425,15 +422,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) { - val canSaveWallets = if (userWalletsListManagerFeatureToggles.isGeneralManagerEnabled) { - runCatching { userWalletsListManager.asLockable()?.isLockedSync } - .fold(onSuccess = { true }, onFailure = { false }) - } else { - userWalletsListManager is BiometricUserWalletsListManager - } - val hasSavedWallets = userWalletsListManager.hasUserWallets + val canSaveWallets = runCatching { userWalletsListManager.asLockable()?.isLockedSync } + .fold(onSuccess = { true }, onFailure = { false }) - if (canSaveWallets && hasSavedWallets) { + if (canSaveWallets && userWalletsListManager.hasUserWallets) { store.dispatch( NavigationAction.NavigateTo( screen = AppScreen.Welcome, diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index cdbf42b9a4..afdb88e7b3 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -39,7 +39,6 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles import com.tangem.features.send.api.featuretoggles.SendFeatureToggles @@ -59,8 +58,6 @@ import com.tangem.tap.common.redux.appReducer import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.tasks.product.DerivationsFinder -import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation -import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation import com.tangem.tap.domain.walletconnect.WalletConnectRepository import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles @@ -149,9 +146,6 @@ internal class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var oneTimeEventFilter: OneTimeEventFilter - @Inject - lateinit var userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles - @Inject lateinit var generalUserWalletsListManager: UserWalletsListManager @@ -204,11 +198,7 @@ internal class TapApplication : Application(), ImageLoaderFactory { runBlocking { featureTogglesManager.init() - if (userWalletsListManagerFeatureToggles.isGeneralManagerEnabled) { - store.dispatch(GlobalAction.UpdateUserWalletsListManager(generalUserWalletsListManager)) - } else { - initUserWalletsListManager() - } + store.dispatch(GlobalAction.UpdateUserWalletsListManager(generalUserWalletsListManager)) blockchainSDKFactory.init() } @@ -256,7 +246,6 @@ internal class TapApplication : Application(), ImageLoaderFactory { balanceHidingRepository = balanceHidingRepository, walletsRepository = walletsRepository, sendFeatureToggles = sendFeatureToggles, - userWalletsListManagerFeatureToggles = userWalletsListManagerFeatureToggles, generalUserWalletsListManager = generalUserWalletsListManager, wasTwinsOnboardingShownUseCase = wasTwinsOnboardingShownUseCase, saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase, @@ -364,14 +353,4 @@ internal class TapApplication : Application(), ImageLoaderFactory { private fun initWarningMessagesManager() { store.dispatch(GlobalAction.SetWarningManager(WarningMessagesManager())) } - - private suspend fun initUserWalletsListManager() { - val manager = if (walletsRepository.shouldSaveUserWalletsSync()) { - UserWalletsListManager.provideBiometricImplementation(applicationContext) - } else { - UserWalletsListManager.provideRuntimeImplementation() - } - - store.dispatch(GlobalAction.UpdateUserWalletsListManager(manager)) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index fbe3e38b89..b3e337e2bf 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -95,17 +95,10 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde ) } is GlobalAction.UpdateUserWalletsListManager -> { - val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles) + val generalUserWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) - if (featureToggles.isGeneralManagerEnabled) { - val generalUserWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) - - appStateHolder.userWalletsListManager = generalUserWalletsListManager - globalState.copy(userWalletsListManager = generalUserWalletsListManager) - } else { - appStateHolder.userWalletsListManager = action.manager - globalState.copy(userWalletsListManager = action.manager) - } + appStateHolder.userWalletsListManager = generalUserWalletsListManager + globalState.copy(userWalletsListManager = generalUserWalletsListManager) } is GlobalAction.ChangeAppThemeMode -> globalState.copy( appThemeMode = action.appThemeMode, diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/DefaultUserWalletsListManagerFeatureToggles.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/DefaultUserWalletsListManagerFeatureToggles.kt deleted file mode 100644 index ca0f135159..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/DefaultUserWalletsListManagerFeatureToggles.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.tap.domain.userWalletList - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles - -internal class DefaultUserWalletsListManagerFeatureToggles( - private val featureTogglesManager: FeatureTogglesManager, -) : UserWalletsListManagerFeatureToggles { - - override val isGeneralManagerEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED") -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerFeatureTogglesModule.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerFeatureTogglesModule.kt deleted file mode 100644 index f94041dc73..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerFeatureTogglesModule.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.tap.domain.userWalletList.di - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles -import com.tangem.tap.domain.userWalletList.DefaultUserWalletsListManagerFeatureToggles -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object UserWalletsListManagerFeatureTogglesModule { - - @Provides - @Singleton - fun provideUserWalletsListManagerFeatureToggles( - featureTogglesManager: FeatureTogglesManager, - ): UserWalletsListManagerFeatureToggles { - return DefaultUserWalletsListManagerFeatureToggles(featureTogglesManager) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt index f2e03181e1..1128c659f5 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt @@ -66,7 +66,7 @@ internal object UserWalletsListManagerModule { val secureStorage = AndroidSecureStorage( preferences = SecureStorage.createEncryptedSharedPreferences( context = applicationContext, - storageName = USER_WALLETS_STORAGE_NAME, + storageName = "user_wallets_storage", ), ) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt deleted file mode 100644 index 51bdd56ae6..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.tangem.tap.domain.userWalletList.di - -import android.content.Context -import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import com.tangem.common.authentication.storage.AuthenticatedStorage -import com.tangem.common.json.TangemSdkAdapter -import com.tangem.common.services.secure.SecureStorage -import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.sdk.storage.AndroidSecureStorage -import com.tangem.sdk.storage.createEncryptedSharedPreferences -import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager -import com.tangem.tap.domain.userWalletList.implementation.RuntimeUserWalletsListManager -import com.tangem.tap.domain.userWalletList.repository.DelegatedKeystoreManager -import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysStoreDecorator -import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricUserWalletsKeysRepository -import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultSelectedUserWalletRepository -import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsPublicInformationRepository -import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsSensitiveInformationRepository -import com.tangem.tap.domain.userWalletList.utils.json.* -import com.tangem.tap.tangemSdkManager -import com.tangem.utils.Provider - -internal const val USER_WALLETS_STORAGE_NAME = "user_wallets_storage" - -fun UserWalletsListManager.Companion.provideBiometricImplementation( - applicationContext: Context, -): UserWalletsListManager { - val moshi = Moshi.Builder() - .add(WalletDerivedKeysMapAdapter()) - .add(ScanResponseDerivedKeysMapAdapter()) - .add(ByteArrayKeyAdapter()) - .add(ExtendedPublicKeysMapAdapter()) - .add(CardBackupStatusAdapter()) - .add(DerivationPathAdapterWithMigration()) - .add(TangemSdkAdapter.DateAdapter()) - .add(TangemSdkAdapter.DerivationNodeAdapter()) - .add(TangemSdkAdapter.FirmwareVersionAdapter()) // For PrimaryCard model - .add(KotlinJsonAdapterFactory()) - .build() - - val secureStorage = AndroidSecureStorage( - preferences = SecureStorage.createEncryptedSharedPreferences( - context = applicationContext, - storageName = USER_WALLETS_STORAGE_NAME, - ), - ) - - val authenticatedStorage = AuthenticatedStorage( - secureStorage = UserWalletsKeysStoreDecorator( - featureStorage = secureStorage, - cardSdkStorageProvider = Provider { tangemSdkManager.secureStorage }, - ), - keystoreManager = DelegatedKeystoreManager( - keystoreManagerProvider = Provider { tangemSdkManager.keystoreManager }, - ), - ) - - val keysRepository = BiometricUserWalletsKeysRepository( - moshi = moshi, - secureStorage = secureStorage, - authenticatedStorage = authenticatedStorage, - ) - val publicInformationRepository = DefaultUserWalletsPublicInformationRepository( - moshi = moshi, - secureStorage = secureStorage, - ) - val sensitiveInformationRepository = DefaultUserWalletsSensitiveInformationRepository( - moshi = moshi, - secureStorage = secureStorage, - ) - val selectedUserWalletRepository = DefaultSelectedUserWalletRepository( - secureStorage = secureStorage, - ) - - return BiometricUserWalletsListManager( - keysRepository = keysRepository, - publicInformationRepository = publicInformationRepository, - sensitiveInformationRepository = sensitiveInformationRepository, - selectedUserWalletRepository = selectedUserWalletRepository, - ) -} - -fun UserWalletsListManager.Companion.provideRuntimeImplementation(): UserWalletsListManager { - return RuntimeUserWalletsListManager() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index dd8119a13d..ab68f8b273 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -5,7 +5,6 @@ import com.tangem.common.* import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError import com.tangem.common.core.UserCodeRequestPolicy -import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.AppScreen @@ -19,7 +18,6 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.domain.userwallets.UserWalletIdBuilder -import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -28,8 +26,6 @@ import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation -import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation 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 @@ -371,49 +367,6 @@ class DetailsMiddleware { private suspend fun saveCurrentWallet( scanResponse: ScanResponse?, enableAccessCodesSaving: Boolean, - ): CompletionResult { - val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles) - - return if (featureToggles.isGeneralManagerEnabled) { - saveCurrentWalletByNewWay(scanResponse, enableAccessCodesSaving) - } else { - saveCurrentWalletByOldWay(scanResponse, enableAccessCodesSaving) - } - } - - private suspend fun saveCurrentWalletByOldWay( - scanResponse: ScanResponse?, - enableAccessCodesSaving: Boolean, - ): CompletionResult { - val userWallet = userWalletsListManager.selectedUserWalletSync - ?: scanResponse?.let { UserWalletBuilder(it).build() } - ?: return CompletionResult.Failure( - error = TangemSdkError.ExceptionError(IllegalStateException("scanResponse is null")), - ) - - updateUserWalletsListManager(enableUserWalletsSaving = true) - - return userWalletsListManager.save(userWallet) - .flatMap { - if (enableAccessCodesSaving) { - saveAccessCodes(scanResponse) - } else { - CompletionResult.Success(Unit) - } - } - .doOnSuccess { - Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On)) - - store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true) - } - .doOnFailure { error -> - Timber.e(error, "Unable to save user wallet") - } - } - - private suspend fun saveCurrentWalletByNewWay( - scanResponse: ScanResponse?, - enableAccessCodesSaving: Boolean, ): CompletionResult { store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true) @@ -431,31 +384,6 @@ class DetailsMiddleware { } private suspend fun deleteSavedWalletsAndAccessCodes(): CompletionResult { - val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles) - - return if (featureToggles.isGeneralManagerEnabled) { - deleteSavedWalletsAndAccessCodesByNewWay() - } else { - deleteSavedWalletsAndAccessCodesByOldWay() - } - } - - private suspend fun deleteSavedWalletsAndAccessCodesByOldWay(): CompletionResult { - return userWalletsListManager.clear() - .doOnSuccess { - Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off)) - deleteSavedAccessCodes() - updateUserWalletsListManager(enableUserWalletsSaving = false) - store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false) - - store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home)) - } - .doOnFailure { error -> - Timber.e(error, "Unable to delete saved wallets") - } - } - - private suspend fun deleteSavedWalletsAndAccessCodesByNewWay(): CompletionResult { Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off)) deleteSavedAccessCodes() @@ -493,25 +421,6 @@ class DetailsMiddleware { Timber.e(error, "Unable to delete saved access codes") } } - - private suspend fun updateUserWalletsListManager(enableUserWalletsSaving: Boolean) { - val manager = if (enableUserWalletsSaving) { - createBiometricsUserWalletsManager() ?: return - } else { - UserWalletsListManager.provideRuntimeImplementation() - } - - store.dispatchWithMain(GlobalAction.UpdateUserWalletsListManager(manager)) - } - - private fun createBiometricsUserWalletsManager(): UserWalletsListManager? { - val context = foregroundActivityObserver.foregroundActivity?.applicationContext.guard { - Timber.e(IllegalStateException("No activities in foreground")) - return null - } - - return UserWalletsListManager.provideBiometricImplementation(context) - } } class AccessCodeRecoveryMiddleware { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index d51111b51a..f17e57ac79 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -90,12 +90,7 @@ object OnboardingHelper { ), ) - val toggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles) - if (toggles.isGeneralManagerEnabled) { - store.dispatchWithMain(SaveWalletAction.SaveWalletAfterBackup(hasBackupError)) - } else { - store.dispatchWithMain(SaveWalletAction.Save) - } + store.dispatchWithMain(SaveWalletAction.SaveWalletAfterBackup(hasBackupError)) } // When should not save user wallets but device has biometry and save wallet screen has not been shown, // then open save wallet screen diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 5c29c96fb9..cfcd3f41af 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -562,7 +562,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) ) }, ) - store.dispatchOnMain(GlobalAction.UpdateUserWalletsListManager(userWalletsListManager)) } val notActivatedCardIds = gatherCardIds(backupState, card).mapNotNull { diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt index acee1bf884..e79ea8b9fe 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt @@ -11,13 +11,11 @@ internal sealed interface SaveWalletAction : Action { val backupCardsIds: Set?, ) : SaveWalletAction - data object Save : SaveWalletAction { + data object AllowToUseBiometrics : SaveWalletAction { data object Success : SaveWalletAction data class Error(val error: TangemError) : SaveWalletAction } - data object AllowToUseBiometrics : SaveWalletAction - data object Dismiss : SaveWalletAction data object CloseError : SaveWalletAction diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 76cb51e2de..563c72cd24 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -7,8 +7,6 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.userwallets.UserWalletBuilder -import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.legacy.isLockable import com.tangem.domain.wallets.models.UserWallet import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -19,8 +17,6 @@ import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn @@ -47,15 +43,14 @@ internal class SaveWalletMiddleware { private fun handleAction(action: SaveWalletAction, state: SaveWalletState) { when (action) { - is SaveWalletAction.Save -> saveWalletIfBiometricsEnrolled(state) is SaveWalletAction.AllowToUseBiometrics -> allowToUseBiometrics(state) is SaveWalletAction.EnrollBiometrics.Enroll -> enrollBiometrics() is SaveWalletAction.Dismiss -> dismiss(state) is SaveWalletAction.SaveWalletAfterBackup -> saveWalletAfterBackup(state, action.hasBackupError) - is SaveWalletAction.Save.Success, + is SaveWalletAction.AllowToUseBiometrics.Success, + is SaveWalletAction.AllowToUseBiometrics.Error, is SaveWalletAction.ProvideBackupInfo, is SaveWalletAction.CloseError, - is SaveWalletAction.Save.Error, is SaveWalletAction.EnrollBiometrics, is SaveWalletAction.EnrollBiometrics.Cancel, -> Unit @@ -91,83 +86,6 @@ internal class SaveWalletMiddleware { store.dispatchOnMain(NavigationAction.OpenBiometricsSettings) } - private fun saveWalletIfBiometricsEnrolled(state: SaveWalletState) { - if (tangemSdkManager.needEnrollBiometrics) { - store.dispatchOnMain(SaveWalletAction.EnrollBiometrics) - } else { - saveWallet(state) - } - } - - /** - - * or from [SaveWalletState.backupInfo] if provided from - * [com.tangem.tap.features.onboarding.OnboardingHelper.trySaveWalletAndNavigateToWalletScreen] - * - * If saved user's wallet was selected then pop back to [AppScreen.Wallet] - * or navigate to [AppScreen.WalletSelector] otherwise - * - * TODO: Update that logic after onboarding and backup features refactoring - * */ - private fun saveWallet(state: SaveWalletState) { - val scanResponse = state.backupInfo?.scanResponse - ?: store.state.globalState.scanResponse - ?: return - - if (state.backupInfo != null) { - // TODO: Remove after onboarding refactoring - Analytics.send(Onboarding.EnableBiometrics(AnalyticsParam.OnOffState.On)) - } else { - Analytics.send(MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.On)) - } - - scope.launch { - val userWallet = userWalletsListManager.selectedUserWalletSync - ?: UserWalletBuilder(scanResponse) - .backupCardsIds(state.backupInfo?.backupCardsIds) - .build() - ?: return@launch - - val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles) - if (!featureToggles.isGeneralManagerEnabled) { - provideLockableUserWalletsListManagerIfNot() - } - - val isFirstSavedWallet = !userWalletsListManager.hasUserWallets - - saveAccessCodeIfNeeded(accessCode = state.backupInfo?.accessCode, cardsInWallet = userWallet.cardsInWallet) - .flatMap { - // Save wallet only at first time (SaveWalletBottomSheet). - // Otherwise (Example, add new wallet in Details) userWalletsListManager.wallets subscribers will - // receive useless updates. - // See: OnboardingHelper.trySaveWalletAndNavigateToWalletScreen() - if (isFirstSavedWallet) { - userWalletsListManager.save(userWallet, canOverride = true) - } else { - CompletionResult.Success(Unit) - } - } - .doOnFailure { error -> - store.dispatchWithMain(SaveWalletAction.Save.Error(error)) - } - .doOnSuccess { - store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true) - - // Enable saving access codes only if this is the first time user save the wallet - if (isFirstSavedWallet) { - store.inject(DaggerGraphState::settingsRepository).setShouldSaveAccessCodes(value = true) - - store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( - isBiometricsRequestPolicy = userWallet.hasAccessCode, - ) - } - - store.dispatchOnMain(SaveWalletAction.Save.Success) - store.navigateToWallet() - } - }.saveIn(saveWalletJobHolder) - } - private fun allowToUseBiometrics(state: SaveWalletState) { if (tangemSdkManager.needEnrollBiometrics) { store.dispatchOnMain(SaveWalletAction.EnrollBiometrics) @@ -189,7 +107,9 @@ internal class SaveWalletMiddleware { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { val error = IllegalStateException("No selected user wallet") Timber.e(error, "Unable to save user wallet") - store.dispatchWithMain(SaveWalletAction.Save.Error(TangemSdkError.ExceptionError(error))) + store.dispatchWithMain( + SaveWalletAction.AllowToUseBiometrics.Error(TangemSdkError.ExceptionError(error)), + ) return@launch } @@ -206,24 +126,10 @@ internal class SaveWalletMiddleware { isBiometricsRequestPolicy = userWallet.hasAccessCode, ) - store.dispatchWithMain(SaveWalletAction.Save.Success) + store.dispatchWithMain(SaveWalletAction.AllowToUseBiometrics.Success) store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet)) } - private suspend fun provideLockableUserWalletsListManagerIfNot() { - if (store.state.globalState.userWalletsListManager?.isLockable == true) return - - val context = foregroundActivityObserver.foregroundActivity?.applicationContext.guard { - val error = IllegalStateException("No activities in foreground") - Timber.e(error) - store.dispatchWithMain(SaveWalletAction.Save.Error(TangemSdkError.ExceptionError(error))) - return - } - val manager = UserWalletsListManager.provideBiometricImplementation(context) - - store.dispatchWithMain(GlobalAction.UpdateUserWalletsListManager(manager)) - } - private fun dismiss(state: SaveWalletState) { if (state.backupInfo != null) { // TODO: Remove after onboarding refactoring diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt index 608f06326e..370f7cd935 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt @@ -21,14 +21,13 @@ internal object SaveWalletReducer { backupCardsIds = action.backupCardsIds, ), ) - is SaveWalletAction.Save, is SaveWalletAction.AllowToUseBiometrics, -> state.copy(isSaveInProgress = true) - is SaveWalletAction.Save.Error -> state.copy( + is SaveWalletAction.AllowToUseBiometrics.Error -> state.copy( error = action.error, isSaveInProgress = false, ) - is SaveWalletAction.Save.Success -> state.copy( + is SaveWalletAction.AllowToUseBiometrics.Success -> state.copy( backupInfo = null, isSaveInProgress = false, ) diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt index 17c05ac8f9..40164976d3 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.settings.SetSaveWalletScreenShownUseCase -import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.features.details.ui.cardsettings.TextReference @@ -24,7 +23,6 @@ import javax.inject.Inject @HiltViewModel internal class SaveWalletViewModel @Inject constructor( - private val userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles, private val analyticsEventHandler: AnalyticsEventHandler, private val setSaveWalletScreenShownUseCase: SetSaveWalletScreenShownUseCase, dispatchers: AppCoroutineDispatcherProvider, @@ -43,12 +41,7 @@ internal class SaveWalletViewModel @Inject constructor( fun saveWallet() { analyticsEventHandler.send(WalletScreenAnalyticsEvent.MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.On)) - - if (userWalletsListManagerFeatureToggles.isGeneralManagerEnabled) { - store.dispatch(SaveWalletAction.AllowToUseBiometrics) - } else { - store.dispatch(SaveWalletAction.Save) - } + store.dispatch(SaveWalletAction.AllowToUseBiometrics) } fun cancelOrClose() { diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index ffbf62495b..9001b33eea 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -18,7 +18,6 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles @@ -60,7 +59,6 @@ data class DaggerGraphState( val sendRouter: SendRouter? = null, val qrScanningRouter: QrScanningRouter? = null, val currenciesRepository: CurrenciesRepository? = null, - val userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles? = null, val generalUserWalletsListManager: UserWalletsListManager? = null, val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase? = null, val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase? = null, diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index c3c879c727..c814055bdc 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -11,10 +11,6 @@ "name": "REDESIGNED_SEND_SCREEN_ENABLED", "version": "undefined" }, - { - "name": "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED", - "version": "5.8.0" - }, { "name": "LOCAL_USER_LOGS_ENABLED", "version": "5.8.0" diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerFeatureToggles.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerFeatureToggles.kt deleted file mode 100644 index d5af7e4449..0000000000 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerFeatureToggles.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.domain.wallets.legacy - -interface UserWalletsListManagerFeatureToggles { - - val isGeneralManagerEnabled: Boolean -} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/WalletsStateHolder.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/WalletsStateHolder.kt index 7aa875c6af..37f6644beb 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/WalletsStateHolder.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/WalletsStateHolder.kt @@ -2,6 +2,8 @@ package com.tangem.domain.wallets.legacy import kotlinx.coroutines.flow.Flow +// TODO: will be remove in this task [REDACTED_JIRA] +@Deprecated(message = "Provide UserWalletsListManager using DI", level = DeprecationLevel.WARNING) interface WalletsStateHolder { val userWalletsListManager: UserWalletsListManager? From 3e5bf1d8c39bdf431e7665c1421f7678c2de88e0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 9 Apr 2024 15:05:44 +0200 Subject: [PATCH 12/98] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + app/src/main/assets/tangem-app-config | 2 +- .../java/com/tangem/tap/di/ActivityModule.kt | 9 +- .../tap/domain/DefaultTangemSdkManager.kt | 290 ++++++++++++++++++ .../tangem/tap/domain/MockTangemSdkManager.kt | 148 +++++++++ .../com/tangem/tap/domain/TangemSdkManager.kt | 227 ++------------ .../card/DefaultDerivationsRepositoryTest.kt | 4 +- core/datasource/build.gradle.kts | 1 + .../datasource/config/models/JsonModels.kt | 1 + .../extension/AppExtensionConfigurations.kt | 1 + .../configuration/model/BuildConfigField.kt | 7 + .../plugin/configuration/model/BuildType.kt | 26 ++ 12 files changed, 505 insertions(+), 212 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/DefaultTangemSdkManager.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/MockTangemSdkManager.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0e20e4fbdc..fdf34a9da6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -219,6 +219,7 @@ dependencies { /** Chucker */ debugImplementation(deps.chucker) + mockedImplementation(deps.chuckerStub) externalImplementation(deps.chuckerStub) internalImplementation(deps.chuckerStub) releaseImplementation(deps.chuckerStub) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 82f4a8aebc..b098560072 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 82f4a8aebcab924047705ad3ecd5f008f07ca0b5 +Subproject commit b0985600722953fae0a93bcbbc2a83930b9e0cbe diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index 2ff65f28a2..c9b778f7c3 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -1,9 +1,12 @@ package com.tangem.tap.di import android.content.Context +import com.tangem.domain.card.BuildConfig import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.exchange.RampStateManager +import com.tangem.tap.domain.DefaultTangemSdkManager +import com.tangem.tap.domain.MockTangemSdkManager import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository import com.tangem.tap.network.exchangeServices.DefaultRampManager @@ -28,7 +31,11 @@ internal object ActivityModule { @ApplicationContext context: Context, cardSdkConfigRepository: CardSdkConfigRepository, ): TangemSdkManager { - return TangemSdkManager(cardSdkConfigRepository = cardSdkConfigRepository, resources = context.resources) + return if (BuildConfig.MOCK_DATA_SOURCE) { + MockTangemSdkManager(resources = context.resources) + } else { + DefaultTangemSdkManager(cardSdkConfigRepository = cardSdkConfigRepository, resources = context.resources) + } } @Provides diff --git a/app/src/main/java/com/tangem/tap/domain/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/DefaultTangemSdkManager.kt new file mode 100644 index 0000000000..979f79f1ca --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/DefaultTangemSdkManager.kt @@ -0,0 +1,290 @@ +package com.tangem.tap.domain + +import android.content.res.Resources +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import com.tangem.Message +import com.tangem.TangemSdk +import com.tangem.common.* +import com.tangem.common.authentication.keystore.KeystoreManager +import com.tangem.common.card.FirmwareVersion +import com.tangem.common.core.* +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.common.services.secure.SecureStorage +import com.tangem.common.usersCode.UserCodeRepository +import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.Basic +import com.tangem.crypto.bip39.DefaultMnemonic +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.common.util.derivationStyleProvider +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.ScanTask +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask +import com.tangem.operations.pins.SetUserCodeCommand +import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.operations.derivation.DeriveWalletPublicKeyTask +import com.tangem.tap.derivationsFinder +import com.tangem.tap.domain.tasks.product.CreateProductWalletTask +import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse +import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask +import com.tangem.tap.domain.tasks.product.ScanProductTask +import com.tangem.wallet.R +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext +import kotlin.coroutines.resume + +@Suppress("TooManyFunctions") +class DefaultTangemSdkManager( + private val cardSdkConfigRepository: CardSdkConfigRepository, + private val resources: Resources, +) : TangemSdkManager { + + private val tangemSdk: TangemSdk + get() = cardSdkConfigRepository.sdk + + private val userCodeRepository by lazy { + UserCodeRepository( + keystoreManager = tangemSdk.keystoreManager, + secureStorage = tangemSdk.secureStorage, + ) + } + + override val canUseBiometry: Boolean + get() = tangemSdk.authenticationManager.canAuthenticate || needEnrollBiometrics + + override val needEnrollBiometrics: Boolean + get() = tangemSdk.authenticationManager.needEnrollBiometrics + + override val keystoreManager: KeystoreManager + get() = tangemSdk.keystoreManager + + override val secureStorage: SecureStorage + get() = tangemSdk.secureStorage + + override val userCodeRequestPolicy: UserCodeRequestPolicy + get() = tangemSdk.config.userCodeRequestPolicy + + override suspend fun scanProduct( + cardId: String?, + messageRes: Int?, + allowsRequestAccessCodeFromRepository: Boolean, + ): CompletionResult { + val message = Message(resources.getString(messageRes ?: R.string.initial_message_scan_header)) + return runTaskAsyncReturnOnMain( + runnable = ScanProductTask( + card = null, + derivationsFinder = derivationsFinder, + allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository, + ), + cardId = cardId, + initialMessage = message, + ).also { sendScanResultsToAnalytics(it) } + } + + override suspend fun createProductWallet( + scanResponse: ScanResponse, + shouldReset: Boolean, + ): CompletionResult { + return runTaskAsync( + runnable = CreateProductWalletTask( + cardTypesResolver = scanResponse.cardTypesResolver, + derivationStyleProvider = scanResponse.derivationStyleProvider, + shouldReset = shouldReset, + ), + cardId = scanResponse.card.cardId, + initialMessage = Message(resources.getString(R.string.initial_message_create_wallet_body)), + iconScanRes = if (scanResponse.cardTypesResolver.isRing()) R.drawable.img_hand_scan_ring else null, + ) + } + + override suspend fun importWallet( + scanResponse: ScanResponse, + mnemonic: String, + passphrase: String?, + shouldReset: Boolean, + ): CompletionResult { + val defaultMnemonic = try { + DefaultMnemonic(mnemonic, tangemSdk.wordlist) + } catch (e: TangemSdkError.MnemonicException) { + return CompletionResult.Failure(e) + } + return runTaskAsync( + CreateProductWalletTask( + cardTypesResolver = scanResponse.cardTypesResolver, + derivationStyleProvider = scanResponse.derivationStyleProvider, + mnemonic = defaultMnemonic, + passphrase = passphrase, + shouldReset = shouldReset, + ), + scanResponse.card.cardId, + Message(resources.getString(R.string.initial_message_create_wallet_body)), + ) + } + + private fun sendScanResultsToAnalytics(result: CompletionResult) { + if (result is CompletionResult.Failure) { + (result.error as? TangemSdkError)?.let { error -> + Analytics.send(Basic.ScanError(error)) + } + } + } + + override suspend fun derivePublicKeys( + cardId: String?, + derivations: Map>, + ): CompletionResult { + return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId) + } + + override suspend fun deriveExtendedPublicKey( + cardId: String?, + walletPublicKey: ByteArray, + derivation: DerivationPath, + ): CompletionResult = withContext(Dispatchers.Main) { + runTaskAsyncReturnOnMain( + DeriveWalletPublicKeyTask(walletPublicKey, derivation), + cardId, + ) + } + + override suspend fun resetToFactorySettings( + cardId: String, + allowsRequestAccessCodeFromRepository: Boolean, + ): CompletionResult { + return runTaskAsyncReturnOnMain( + runnable = ResetToFactorySettingsTask( + allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository, + ), + cardId = cardId, + initialMessage = Message(resources.getString(R.string.card_settings_reset_card_to_factory)), + ) + .map { CardDTO(it) } + } + + override suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult { + return userCodeRepository.save( + cardsIds = cardsIds, + userCode = UserCode( + type = UserCodeType.AccessCode, + stringValue = accessCode, + ), + ) + } + + override suspend fun deleteSavedUserCodes(cardsIds: Set): CompletionResult { + return userCodeRepository.delete(cardsIds.toSet()) + } + + override suspend fun clearSavedUserCodes(): CompletionResult { + return userCodeRepository.clear() + } + + override suspend fun setPasscode(cardId: String?): CompletionResult { + return runTaskAsyncReturnOnMain( + SetUserCodeCommand.changePasscode(null), + cardId, + initialMessage = Message(resources.getString(R.string.initial_message_change_passcode_body)), + ) + } + + override suspend fun setAccessCode(cardId: String?): CompletionResult { + return runTaskAsyncReturnOnMain( + SetUserCodeCommand.changeAccessCode(null), + cardId, + initialMessage = Message(resources.getString(R.string.initial_message_change_access_code_body)), + ) + } + + override suspend fun setLongTap(cardId: String?): CompletionResult { + return runTaskAsyncReturnOnMain( + SetUserCodeCommand.resetUserCodes(), + cardId, + initialMessage = Message(resources.getString(R.string.initial_message_tap_header)), + ) + } + + override suspend fun setAccessCodeRecoveryEnabled( + cardId: String?, + enabled: Boolean, + ): CompletionResult { + return runTaskAsyncReturnOnMain( + SetUserCodeRecoveryAllowedTask(enabled), + cardId, + initialMessage = Message(resources.getString(R.string.initial_message_tap_header)), + ) + } + + override suspend fun scanCard( + cardId: String?, + allowRequestAccessCodeFromRepository: Boolean, + ): CompletionResult { + return runTaskAsyncReturnOnMain( + runnable = ScanTask(allowRequestAccessCodeFromRepository), + cardId = cardId, + initialMessage = Message(resources.getString(R.string.initial_message_tap_header)), + ) + .map { CardDTO(it) } + } + + override suspend fun runTaskAsync( + runnable: CardSessionRunnable, + cardId: String?, + initialMessage: Message?, + accessCode: String?, + @DrawableRes iconScanRes: Int?, + ): CompletionResult = withContext(Dispatchers.Main) { + suspendCancellableCoroutine { continuation -> + tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage, accessCode, iconScanRes) { result -> + if (continuation.isActive) continuation.resume(result) + } + } + } + + private suspend fun runTaskAsyncReturnOnMain( + runnable: CardSessionRunnable, + cardId: String? = null, + initialMessage: Message? = null, + ): CompletionResult { + val result = runTaskAsync(runnable, cardId, initialMessage) + return withContext(Dispatchers.Main) { result } + } + + @Suppress("MagicNumber") + override fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) { + tangemSdk.config.cardIdDisplayFormat = when { + scanResponse == null -> CardIdDisplayFormat.Full + scanResponse.cardTypesResolver.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4) + else -> CardIdDisplayFormat.Full + } + } + + @Deprecated("TangemSdkManager shouldn't returns a string from resources") + override fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String { + return resources.getString(stringResId, *formatArgs) + } + + override fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) { + tangemSdk.config.userCodeRequestPolicy = policy + } + + companion object { + @Deprecated("Use [DefaultCardSdkProvider] instead") + val config = Config( + linkedTerminal = true, + allowUntrustedCards = true, + filter = CardFilter( + allowedCardTypes = FirmwareVersion.FirmwareType.values().toList(), + maxFirmwareVersion = FirmwareVersion(major = 6, minor = 33), + batchIdFilter = CardFilter.Companion.ItemFilter.Deny( + items = setOf("0027", "0030", "0031", "0035"), + ), + ), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/MockTangemSdkManager.kt new file mode 100644 index 0000000000..0981871a0f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/MockTangemSdkManager.kt @@ -0,0 +1,148 @@ +package com.tangem.tap.domain + +import android.content.res.Resources +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import com.tangem.Message +import com.tangem.common.* +import com.tangem.common.authentication.keystore.KeystoreManager +import com.tangem.common.core.* +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.common.services.secure.SecureStorage +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +@Suppress("TooManyFunctions") +class MockTangemSdkManager( + private val resources: Resources, +) : TangemSdkManager { + + override val canUseBiometry: Boolean + get() = TODO() + + override val needEnrollBiometrics: Boolean + get() = TODO() + + override val keystoreManager: KeystoreManager + get() = TODO() + + override val secureStorage: SecureStorage + get() = TODO() + + override val userCodeRequestPolicy: UserCodeRequestPolicy + get() = TODO() + + override suspend fun scanProduct( + cardId: String?, + messageRes: Int?, + allowsRequestAccessCodeFromRepository: Boolean, + ): CompletionResult { + TODO() + } + + override suspend fun createProductWallet( + scanResponse: ScanResponse, + shouldReset: Boolean, + ): CompletionResult { + TODO() + } + + override suspend fun importWallet( + scanResponse: ScanResponse, + mnemonic: String, + passphrase: String?, + shouldReset: Boolean, + ): CompletionResult { + TODO() + } + + override suspend fun derivePublicKeys( + cardId: String?, + derivations: Map>, + ): CompletionResult { + TODO() + } + + override suspend fun deriveExtendedPublicKey( + cardId: String?, + walletPublicKey: ByteArray, + derivation: DerivationPath, + ): CompletionResult { + TODO() + } + + override suspend fun resetToFactorySettings( + cardId: String, + allowsRequestAccessCodeFromRepository: Boolean, + ): CompletionResult { + TODO() + } + + override suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult { + TODO() + } + + override suspend fun deleteSavedUserCodes(cardsIds: Set): CompletionResult { + TODO() + } + + override suspend fun clearSavedUserCodes(): CompletionResult { + TODO() + } + + override suspend fun setPasscode(cardId: String?): CompletionResult { + TODO() + } + + override suspend fun setAccessCode(cardId: String?): CompletionResult { + TODO() + } + + override suspend fun setLongTap(cardId: String?): CompletionResult { + TODO() + } + + override suspend fun setAccessCodeRecoveryEnabled( + cardId: String?, + enabled: Boolean, + ): CompletionResult { + TODO() + } + + override suspend fun scanCard( + cardId: String?, + allowRequestAccessCodeFromRepository: Boolean, + ): CompletionResult { + TODO() + } + + override suspend fun runTaskAsync( + runnable: CardSessionRunnable, + cardId: String?, + initialMessage: Message?, + accessCode: String?, + @DrawableRes iconScanRes: Int?, + ): CompletionResult = withContext(Dispatchers.Main) { + TODO() + } + + @Suppress("MagicNumber") + override fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) { + TODO() + } + + @Deprecated("TangemSdkManager shouldn't returns a string from resources") + override fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String { + return resources.getString(stringResId, *formatArgs) + } + + override fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) { + TODO() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 4097061289..6df4d03090 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -1,233 +1,84 @@ package com.tangem.tap.domain -import android.content.res.Resources import androidx.annotation.DrawableRes import androidx.annotation.StringRes import com.tangem.Message -import com.tangem.TangemSdk import com.tangem.common.* import com.tangem.common.authentication.keystore.KeystoreManager -import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.* import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.services.secure.SecureStorage -import com.tangem.common.usersCode.UserCodeRepository -import com.tangem.core.analytics.Analytics -import com.tangem.core.analytics.models.Basic -import com.tangem.crypto.bip39.DefaultMnemonic import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse -import com.tangem.operations.ScanTask import com.tangem.operations.derivation.DerivationTaskResponse -import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask -import com.tangem.operations.pins.SetUserCodeCommand -import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey -import com.tangem.operations.derivation.DeriveWalletPublicKeyTask -import com.tangem.tap.derivationsFinder -import com.tangem.tap.domain.tasks.product.CreateProductWalletTask import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse -import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask -import com.tangem.tap.domain.tasks.product.ScanProductTask -import com.tangem.wallet.R -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withContext -import kotlin.coroutines.resume -@Suppress("TooManyFunctions") -class TangemSdkManager( - private val cardSdkConfigRepository: CardSdkConfigRepository, - private val resources: Resources, -) { - - private val tangemSdk: TangemSdk - get() = cardSdkConfigRepository.sdk - - private val userCodeRepository by lazy { - UserCodeRepository( - keystoreManager = tangemSdk.keystoreManager, - secureStorage = tangemSdk.secureStorage, - ) - } +interface TangemSdkManager { val canUseBiometry: Boolean - get() = tangemSdk.authenticationManager.canAuthenticate || needEnrollBiometrics val needEnrollBiometrics: Boolean - get() = tangemSdk.authenticationManager.needEnrollBiometrics val keystoreManager: KeystoreManager - get() = tangemSdk.keystoreManager val secureStorage: SecureStorage - get() = tangemSdk.secureStorage val userCodeRequestPolicy: UserCodeRequestPolicy - get() = tangemSdk.config.userCodeRequestPolicy suspend fun scanProduct( cardId: String? = null, messageRes: Int? = null, allowsRequestAccessCodeFromRepository: Boolean = false, - ): CompletionResult { - val message = Message(resources.getString(messageRes ?: R.string.initial_message_scan_header)) - return runTaskAsyncReturnOnMain( - runnable = ScanProductTask( - card = null, - derivationsFinder = derivationsFinder, - allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository, - ), - cardId = cardId, - initialMessage = message, - ).also { sendScanResultsToAnalytics(it) } - } + ): CompletionResult suspend fun createProductWallet( scanResponse: ScanResponse, shouldReset: Boolean = false, - ): CompletionResult { - return runTaskAsync( - runnable = CreateProductWalletTask( - cardTypesResolver = scanResponse.cardTypesResolver, - derivationStyleProvider = scanResponse.derivationStyleProvider, - shouldReset = shouldReset, - ), - cardId = scanResponse.card.cardId, - initialMessage = Message(resources.getString(R.string.initial_message_create_wallet_body)), - iconScanRes = if (scanResponse.cardTypesResolver.isRing()) R.drawable.img_hand_scan_ring else null, - ) - } + ): CompletionResult suspend fun importWallet( scanResponse: ScanResponse, mnemonic: String, passphrase: String?, shouldReset: Boolean, - ): CompletionResult { - val defaultMnemonic = try { - DefaultMnemonic(mnemonic, tangemSdk.wordlist) - } catch (e: TangemSdkError.MnemonicException) { - return CompletionResult.Failure(e) - } - return runTaskAsync( - CreateProductWalletTask( - cardTypesResolver = scanResponse.cardTypesResolver, - derivationStyleProvider = scanResponse.derivationStyleProvider, - mnemonic = defaultMnemonic, - passphrase = passphrase, - shouldReset = shouldReset, - ), - scanResponse.card.cardId, - Message(resources.getString(R.string.initial_message_create_wallet_body)), - ) - } - - private fun sendScanResultsToAnalytics(result: CompletionResult) { - if (result is CompletionResult.Failure) { - (result.error as? TangemSdkError)?.let { error -> - Analytics.send(Basic.ScanError(error)) - } - } - } + ): CompletionResult suspend fun derivePublicKeys( cardId: String?, derivations: Map>, - ): CompletionResult { - return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId) - } + ): CompletionResult suspend fun deriveExtendedPublicKey( cardId: String?, walletPublicKey: ByteArray, derivation: DerivationPath, - ): CompletionResult = withContext(Dispatchers.Main) { - runTaskAsyncReturnOnMain( - DeriveWalletPublicKeyTask(walletPublicKey, derivation), - cardId, - ) - } + ): CompletionResult suspend fun resetToFactorySettings( cardId: String, allowsRequestAccessCodeFromRepository: Boolean, - ): CompletionResult { - return runTaskAsyncReturnOnMain( - runnable = ResetToFactorySettingsTask( - allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository, - ), - cardId = cardId, - initialMessage = Message(resources.getString(R.string.card_settings_reset_card_to_factory)), - ) - .map { CardDTO(it) } - } + ): CompletionResult - suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult { - return userCodeRepository.save( - cardsIds = cardsIds, - userCode = UserCode( - type = UserCodeType.AccessCode, - stringValue = accessCode, - ), - ) - } + suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult - suspend fun deleteSavedUserCodes(cardsIds: Set): CompletionResult { - return userCodeRepository.delete(cardsIds.toSet()) - } + suspend fun deleteSavedUserCodes(cardsIds: Set): CompletionResult - suspend fun clearSavedUserCodes(): CompletionResult { - return userCodeRepository.clear() - } + suspend fun clearSavedUserCodes(): CompletionResult - suspend fun setPasscode(cardId: String?): CompletionResult { - return runTaskAsyncReturnOnMain( - SetUserCodeCommand.changePasscode(null), - cardId, - initialMessage = Message(resources.getString(R.string.initial_message_change_passcode_body)), - ) - } + suspend fun setPasscode(cardId: String?): CompletionResult - suspend fun setAccessCode(cardId: String?): CompletionResult { - return runTaskAsyncReturnOnMain( - SetUserCodeCommand.changeAccessCode(null), - cardId, - initialMessage = Message(resources.getString(R.string.initial_message_change_access_code_body)), - ) - } + suspend fun setAccessCode(cardId: String?): CompletionResult - suspend fun setLongTap(cardId: String?): CompletionResult { - return runTaskAsyncReturnOnMain( - SetUserCodeCommand.resetUserCodes(), - cardId, - initialMessage = Message(resources.getString(R.string.initial_message_tap_header)), - ) - } + suspend fun setLongTap(cardId: String?): CompletionResult - suspend fun setAccessCodeRecoveryEnabled(cardId: String?, enabled: Boolean): CompletionResult { - return runTaskAsyncReturnOnMain( - SetUserCodeRecoveryAllowedTask(enabled), - cardId, - initialMessage = Message(resources.getString(R.string.initial_message_tap_header)), - ) - } + suspend fun setAccessCodeRecoveryEnabled(cardId: String?, enabled: Boolean): CompletionResult suspend fun scanCard( cardId: String? = null, allowRequestAccessCodeFromRepository: Boolean = false, - ): CompletionResult { - return runTaskAsyncReturnOnMain( - runnable = ScanTask(allowRequestAccessCodeFromRepository), - cardId = cardId, - initialMessage = Message(resources.getString(R.string.initial_message_tap_header)), - ) - .map { CardDTO(it) } - } + ): CompletionResult suspend fun runTaskAsync( runnable: CardSessionRunnable, @@ -235,53 +86,13 @@ class TangemSdkManager( initialMessage: Message? = null, accessCode: String? = null, @DrawableRes iconScanRes: Int? = null, - ): CompletionResult = withContext(Dispatchers.Main) { - suspendCancellableCoroutine { continuation -> - tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage, accessCode, iconScanRes) { result -> - if (continuation.isActive) continuation.resume(result) - } - } - } - - private suspend fun runTaskAsyncReturnOnMain( - runnable: CardSessionRunnable, - cardId: String? = null, - initialMessage: Message? = null, - ): CompletionResult { - val result = runTaskAsync(runnable, cardId, initialMessage) - return withContext(Dispatchers.Main) { result } - } + ): CompletionResult @Suppress("MagicNumber") - fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) { - tangemSdk.config.cardIdDisplayFormat = when { - scanResponse == null -> CardIdDisplayFormat.Full - scanResponse.cardTypesResolver.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4) - else -> CardIdDisplayFormat.Full - } - } + fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) @Deprecated("TangemSdkManager shouldn't returns a string from resources") - fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String { - return resources.getString(stringResId, *formatArgs) - } + fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String - fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) { - tangemSdk.config.userCodeRequestPolicy = policy - } - - companion object { - @Deprecated("Use [DefaultCardSdkProvider] instead") - val config = Config( - linkedTerminal = true, - allowUntrustedCards = true, - filter = CardFilter( - allowedCardTypes = FirmwareVersion.FirmwareType.values().toList(), - maxFirmwareVersion = FirmwareVersion(major = 6, minor = 33), - batchIdFilter = CardFilter.Companion.ItemFilter.Deny( - items = setOf("0027", "0030", "0031", "0035"), - ), - ), - ) - } + fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) } \ No newline at end of file diff --git a/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt index b1e1189c8e..a7b0038bf6 100644 --- a/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt @@ -10,7 +10,7 @@ import com.tangem.domain.common.configs.MultiWalletCardConfig import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse -import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.DefaultTangemSdkManager import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import io.mockk.* import kotlinx.coroutines.test.runTest @@ -21,7 +21,7 @@ import org.junit.Test */ internal class DefaultDerivationsRepositoryTest { - private val tangemSdkManager = mockk() + private val tangemSdkManager = mockk() private val userWalletsStore = mockk() private val repository = DefaultDerivationsRepository( tangemSdkManager = tangemSdkManager, diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index e4239e40d6..6aff99a356 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -53,6 +53,7 @@ dependencies { /** Chucker */ debugImplementation(deps.chucker) + mockedImplementation(deps.chuckerStub) externalImplementation(deps.chuckerStub) internalImplementation(deps.chuckerStub) releaseImplementation(deps.chuckerStub) diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt index 0e572bd3e5..2a79daad20 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt @@ -7,6 +7,7 @@ import com.squareup.moshi.JsonClass [REDACTED_AUTHOR] */ +// TODO remove class FeatureModel( val isTopUpEnabled: Boolean, val isCreatingTwinCardsAllowed: Boolean, diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt index f288d89a7d..6975388bc0 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt @@ -76,6 +76,7 @@ private fun AndroidBuildType.configureBuildVariant(extension: AppExtension, buil } BuildType.Internal, BuildType.External, + BuildType.Mocked -> { initWith(extension.buildTypes.getByName(BuildType.Release.id)) matchingFallbacks.add(BuildType.Release.id) diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildConfigField.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildConfigField.kt index 66437a1de5..17f61ecdd2 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildConfigField.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildConfigField.kt @@ -15,6 +15,7 @@ internal sealed class BuildConfigField(val type: String, val name: String, val v value = "\"$value\"", ) + // TODO remove class TestActionEnabled(isEnabled: Boolean) : BuildConfigField( type = "Boolean", name = "TEST_ACTION_ENABLED", @@ -32,4 +33,10 @@ internal sealed class BuildConfigField(val type: String, val name: String, val v name = "TESTER_MENU_ENABLED", value = isEnabled.toString(), ) + + class MockDataSource(isEnabled: Boolean) : BuildConfigField( + type = "Boolean", + name = "MOCK_DATA_SOURCE", + value = isEnabled.toString(), + ) } \ No newline at end of file diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt index 767e46ba25..53a1c18637 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt @@ -27,6 +27,29 @@ internal enum class BuildType( BuildConfigField.TestActionEnabled(isEnabled = true), BuildConfigField.LogEnabled(isEnabled = true), BuildConfigField.TesterMenuAvailability(isEnabled = true), + BuildConfigField.MockDataSource(isEnabled = false), + ), + ), + + /** + * Build type for QA and business + * + * Features: + * - Env: dev + * - Signing config: debug + * - Logs + * - Enabled mocked datasource + * */ + Mocked( + id = "mocked", + appIdSuffix = "mocked", + versionSuffix = "mocked", + configFields = listOf( + BuildConfigField.Environment(value = "dev"), + BuildConfigField.TestActionEnabled(isEnabled = false), + BuildConfigField.LogEnabled(isEnabled = true), + BuildConfigField.TesterMenuAvailability(isEnabled = false), + BuildConfigField.MockDataSource(isEnabled = true), ), ), @@ -50,6 +73,7 @@ internal enum class BuildType( BuildConfigField.TestActionEnabled(isEnabled = true), BuildConfigField.LogEnabled(isEnabled = true), BuildConfigField.TesterMenuAvailability(isEnabled = true), + BuildConfigField.MockDataSource(isEnabled = false), ), ), @@ -70,6 +94,7 @@ internal enum class BuildType( BuildConfigField.TestActionEnabled(isEnabled = false), BuildConfigField.LogEnabled(isEnabled = false), BuildConfigField.TesterMenuAvailability(isEnabled = false), + BuildConfigField.MockDataSource(isEnabled = false), ), ), @@ -88,6 +113,7 @@ internal enum class BuildType( BuildConfigField.TestActionEnabled(isEnabled = false), BuildConfigField.LogEnabled(isEnabled = false), BuildConfigField.TesterMenuAvailability(isEnabled = false), + BuildConfigField.MockDataSource(isEnabled = false), ), ), } \ No newline at end of file From afe5ec2c57cc975d1f585d7680adf3839180b6d7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 3 Apr 2024 19:35:00 +0100 Subject: [PATCH 13/98] Updated on 2026-08-14 --- app/build.gradle.kts | 1 - .../java/com/tangem/tap/TapApplication.kt | 4 - .../com/tangem/tap/common/DialogManager.kt | 4 - .../com/tangem/tap/domain/TapWalletManager.kt | 3 - .../tap/domain/walletconnect/BnbHelper.kt | 50 +- .../walletconnect/WalletConnectManager.kt | 562 ------------------ .../WalletConnectNetworkUtils.kt | 41 -- .../walletconnect/WalletConnectRepository.kt | 105 ---- .../walletconnect/WalletConnectSdkHelper.kt | 6 +- .../walletconnect/extensions/WcPeerMeta.kt | 39 -- .../walletconnect/WalletConnectAction.kt | 100 +--- .../walletconnect/WalletConnectMiddleware.kt | 320 +--------- .../walletconnect/WalletConnectReducer.kt | 27 - .../redux/walletconnect/WalletConnectState.kt | 5 - .../ui/walletconnect/WalletConnectFragment.kt | 8 - .../walletconnect/WalletConnectScreenState.kt | 12 +- .../walletconnect/WalletConnectViewModel.kt | 19 +- .../dialogs/ApproveWcSessionDialog.kt | 45 -- .../dialogs/BnbTransactionDialog.kt | 7 - .../dialogs/ChooseNetworkDialog.kt | 35 -- .../dialogs/PersonalSignDialog.kt | 1 - .../dialogs/TransactionDialog.kt | 2 - .../WalletConnectLinkIntentHandler.kt | 10 +- 23 files changed, 23 insertions(+), 1383 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ChooseNetworkDialog.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fdf34a9da6..16b9ab3d1d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -42,7 +42,6 @@ configurations.all { dependencies { - implementation(files("libs/walletconnect-1.5.6.aar")) implementation(projects.domain.legacy) implementation(projects.domain.models) implementation(projects.domain.core) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index afdb88e7b3..933d29882a 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -58,7 +58,6 @@ import com.tangem.tap.common.redux.appReducer import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.tasks.product.DerivationsFinder -import com.tangem.tap.domain.walletconnect.WalletConnectRepository import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles import com.tangem.tap.proxy.AppStateHolder @@ -76,7 +75,6 @@ lateinit var store: Store lateinit var foregroundActivityObserver: ForegroundActivityObserver lateinit var activityResultCaller: ActivityResultCaller -lateinit var walletConnectRepository: WalletConnectRepository internal lateinit var derivationsFinder: DerivationsFinder @HiltAndroidApp @@ -191,8 +189,6 @@ internal class TapApplication : Application(), ImageLoaderFactory { activityResultCaller = foregroundActivityObserver registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks) - walletConnectRepository = WalletConnectRepository(this) - // TODO: Try to performance and user experience. // [REDACTED_JIRA] runBlocking { diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index cbbcee8526..d27bc8388c 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -86,10 +86,6 @@ class DialogManager : StoreSubscriber { context = context, ) } - is WalletConnectDialog.ApproveWcSession -> - ApproveWcSessionDialog.create(state.dialog.session, state.dialog.networks, context) - is WalletConnectDialog.ChooseNetwork -> - ChooseNetworkDialog.create(state.dialog.session, state.dialog.networks, context) is WalletConnectDialog.ClipboardOrScanQr -> ClipboardOrScanQrDialog.create(state.dialog.clipboardUri, context) is WalletConnectDialog.RequestTransaction -> TransactionDialog.create(state.dialog.data, context) diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 7a799500ef..14f5f5e725 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -12,7 +12,6 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.operations.attestation.Attestation import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.disclaimer.createDisclaimer import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction @@ -58,9 +57,7 @@ class TapWalletManager( // Order is important store.dispatch(DisclaimerAction.SetDisclaimer(card.createDisclaimer())) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) - store.dispatch(WalletConnectAction.ResetState) store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) - store.dispatch(WalletConnectAction.RestoreSessions(scanResponse)) store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed)) } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt index 16a35bf539..2b3094d9f6 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt @@ -10,13 +10,11 @@ import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTrans import com.tangem.tap.domain.walletconnect2.domain.models.binance.tradeOrderSerializer import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData import com.tangem.tap.features.details.redux.walletconnect.TradeData -import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder -import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder import timber.log.Timber internal object BnbHelper { - fun createMessageData(order: WCBinanceTransferOrder): BinanceMessageData.Transfer { + fun createMessageData(order: WcBinanceTransferOrder): BinanceMessageData.Transfer { val input = order.msgs.first().inputs.first() val output = order.msgs.first().inputs.first() @@ -42,51 +40,7 @@ internal object BnbHelper { ) } - fun WcBinanceTradeOrder.toWCBinanceTradeOrder(): WCBinanceTradeOrder { - return WCBinanceTradeOrder( - account_number = accountNumber, - chain_id = chainId, - data = data, - memo = memo, - sequence = sequence, - source = source, - msgs = msgs.map { it.toWCBinanceTradeOrderMessage() }, - ) - } - - private fun WcBinanceTradeOrder.Message.toWCBinanceTradeOrderMessage(): WCBinanceTradeOrder.Message { - return WCBinanceTradeOrder.Message(id, orderType, price, quantity, sender, side, symbol, timeInforce) - } - - fun WcBinanceTransferOrder.toWCBinanceTransferOrder(): WCBinanceTransferOrder { - return WCBinanceTransferOrder( - account_number = accountNumber, - chain_id = chainId, - data = data, - memo = memo, - sequence = sequence, - source = source, - msgs = msgs.map { it.toWCBinanceTransferOrderMessage() }, - ) - } - - private fun WcBinanceTransferOrder.Message.toWCBinanceTransferOrderMessage(): WCBinanceTransferOrder.Message { - return WCBinanceTransferOrder.Message( - inputs.map { it.toWCBinanceItem() }, - outputs.map { it.toWCBinanceItem() }, - ) - } - - private fun WcBinanceTransferOrder.Message.Item.toWCBinanceItem(): WCBinanceTransferOrder.Message.Item { - return WCBinanceTransferOrder.Message.Item( - address, - coins.map { - WCBinanceTransferOrder.Message.Item.Coin(it.amount, it.denom) - }, - ) - } - - fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade { + fun createMessageData(order: WcBinanceTradeOrder): BinanceMessageData.Trade { val address = order.msgs.first().sender val tradeData = order.msgs.map { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt deleted file mode 100644 index 9078975c2c..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ /dev/null @@ -1,562 +0,0 @@ -package com.tangem.tap.domain.walletconnect - -import com.tangem.blockchain.common.Blockchain -import com.tangem.common.card.EllipticCurve -import com.tangem.common.extensions.guard -import com.tangem.datasource.api.common.createNetworkLoggingInterceptor -import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.walletconnect.extensions.isDappSupported -import com.tangem.tap.domain.walletconnect.extensions.toWcEthereumSignMessage -import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction -import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest -import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData -import com.tangem.tap.features.details.redux.walletconnect.* -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.walletConnectRepository -import com.trustwallet.walletconnect.WCClient -import com.trustwallet.walletconnect.models.WCPeerMeta -import com.trustwallet.walletconnect.models.binance.WCBinanceCancelOrder -import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder -import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder -import com.trustwallet.walletconnect.models.binance.WCBinanceTxConfirmParam -import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage -import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction -import com.trustwallet.walletconnect.models.session.WCAddNetwork -import com.trustwallet.walletconnect.models.session.WCSession -import com.trustwallet.walletconnect.models.session.WCSessionUpdate -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import timber.log.Timber -import java.util.UUID -import java.util.concurrent.TimeUnit -import kotlin.collections.set - -@Suppress("LargeClass") -internal class WalletConnectManager { - - private var cardId: String? = null - - private val okHttpClient: OkHttpClient by lazy { - OkHttpClient.Builder() - .connectTimeout(20, TimeUnit.SECONDS) - .readTimeout(20, TimeUnit.SECONDS) - .writeTimeout(20, TimeUnit.SECONDS) - .addInterceptor(interceptor) - .addInterceptor(RetryInterceptor()) - .build() - } - - private val interceptor by lazy { - createNetworkLoggingInterceptor() - } - - private var sessions: MutableMap = mutableMapOf() - - fun connect(wcUri: String) { - val session = WCSession.from(wcUri).guard { - store.dispatchOnMain( - WalletConnectAction.FailureEstablishingSession( - session = null, - error = TapError.WalletConnect.UnsupportedLink, - ), - ) - return - } - if (sessions[session.topic] != null) { - store.dispatchOnMain(WalletConnectAction.RefuseOpeningSession) - return - } - val client = WCClient(httpClient = okHttpClient) - setListeners(client) - val peerId = UUID.randomUUID().toString() - - try { - client.connect(session, tangemPeerMeta, peerId) - } catch (exception: IllegalArgumentException) { - store.dispatchOnMain( - WalletConnectAction.FailureEstablishingSession( - session = null, - error = TapError.WalletConnect.UnsupportedLink, - ), - ) - return - } - - sessions[session.topic] = WalletConnectActiveData( - peerId = peerId, - remotePeerId = null, - session = session, - client = client, - wallet = WalletForSession(), - ) - setupConnectionTimeoutCheck(session) - } - - fun updateSession(session: WalletConnectSession) { - val updatedSession = sessions[session.session.topic]?.copy( - wallet = session.wallet, - ) - if (updatedSession != null) { - sessions[session.session.topic] = updatedSession - } - } - - fun updateBlockchain(session: WalletConnectSession) { - sessions[session.session.topic]?.client?.updateSession( - accounts = listOfNotNull(session.getAddress()), - chainId = session.wallet.blockchain?.getChainId(), - approved = true, - ) - - val updatedSession = sessions[session.session.topic]?.copy( - wallet = session.wallet, - ) - if (updatedSession != null) { - sessions[session.session.topic] = updatedSession - } - } - - @Suppress("MagicNumber") - private fun setupConnectionTimeoutCheck(session: WCSession) { - scope.launch { - delay(20_000) - val data = sessions[session.topic] - if (data != null && data.peerMeta == null) { - disconnect(session) - store.dispatchOnMain(WalletConnectAction.OpeningSessionTimeout(session)) - } - } - } - - fun restoreSessions(scanResponse: ScanResponse) { - val walletPublicKey = scanResponse.card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }?.publicKey - ?: return - if (scanResponse.card.backupStatus?.isActive != true) cardId = scanResponse.card.cardId - val sessions = walletConnectRepository.loadSavedSessions() - // filter sessions for this particular card - .filter { it.wallet.walletPublicKey.contentEquals(walletPublicKey) } - this.sessions = sessions - .map { session -> - WalletConnectActiveData( - peerId = session.peerId, - remotePeerId = session.remotePeerId, - client = WCClient(httpClient = okHttpClient), - session = session.session, - peerMeta = session.peerMeta, - wallet = session.wallet, - ) - .also { - setListeners(it.client) - it.client.connect(it.session, tangemPeerMeta, it.peerId, it.remotePeerId) - } - }.associateBy { it.session.topic }.toMutableMap() - - store.dispatchOnMain(WalletConnectAction.SetSessionsRestored(sessions)) - } - - fun approve(session: WCSession) { - val activeData = sessions[session.topic] ?: return - removeSimilarSessions(activeData) - - val key = activeData.wallet.derivedPublicKey ?: activeData.wallet.walletPublicKey ?: return - val blockchain = activeData.wallet.getBlockchainForSession() - val accounts = listOf(blockchain.makeAddresses(key).first().value) - val approved = activeData.client.approveSession( - accounts = accounts, - chainId = blockchain.getChainId() ?: Blockchain.Ethereum.getChainId()!!, - ) - if (approved) { - val walletConnectSession = WalletConnectSession( - peerId = activeData.peerId, - remotePeerId = activeData.remotePeerId, - wallet = activeData.wallet, - session = session, - peerMeta = activeData.peerMeta!!, - ) - walletConnectRepository.saveSession(walletConnectSession) - store.dispatchOnMain( - WalletConnectAction.ApproveSession.Success( - walletConnectSession, - ), - ) - } - } - - private fun removeSimilarSessions(activeData: WalletConnectActiveData) { - val sessionsToRemove = sessions.filter { - it.value.wallet.walletPublicKey?.equals(activeData.wallet.walletPublicKey) == true && - it.value.peerMeta?.url == activeData.peerMeta?.url && - it.value.session != activeData.session - } - Timber.d("RemoveSimilarSessions: ${sessionsToRemove.values.map { it.client.session }}") - sessionsToRemove.forEach { disconnect(it.value.session) } - } - - fun rejectRequest(topic: String, id: Long) { - val activeData = sessions[topic] ?: return - activeData.client.rejectRequest(id) - } - - private fun acceptRequest(topic: String, id: Long, data: String) { - val activeData = sessions[topic] ?: return - activeData.client.approveRequest(id, data) - } - - fun disconnect(session: WCSession) { - val activeData = sessions[session.topic] ?: return - val disconnected = if (activeData.client.isConnected) { - activeData.client.killSession() - } else { - true - } - - if (disconnected) { - onSessionClosed(session) - } - } - - private fun onSessionClosed(session: WCSession) { - sessions.remove(session.topic) - walletConnectRepository.removeSession(session) - store.dispatchOnMain(WalletConnectAction.RemoveSession(session)) - } - - fun handleTransactionRequest( - transaction: WcEthereumTransaction, - session: WalletConnectSession, - id: Long, - type: WcEthTransactionType, - ) { - val activeData = sessions[session.session.topic] ?: return - scope.launch { - val data = WalletConnectSdkHelper().prepareTransactionData( - EthTransactionData( - transaction = transaction, - networkId = session.wallet.blockchain?.toNetworkId() ?: "", - rawDerivationPath = session.wallet.derivationPath?.rawPath, - id = id, - topic = session.session.topic, - type = type, - metaName = session.peerMeta.name, - metaUrl = session.peerMeta.url, - ), - ).guard { - sessions[session.session.topic] = activeData.copy(transactionData = null) - store.dispatchOnMain( - WalletConnectAction.RejectRequest( - session.session.topic, - id, - ), - ) - return@launch - } - sessions[session.session.topic] = activeData.copy(transactionData = data) - - store.dispatchOnMain( - GlobalAction.ShowDialog( - WalletConnectDialog.RequestTransaction( - WcPreparedRequest.EthTransaction( - preparedRequestData = data, - topic = session.session.topic, - requestId = id, - derivationPath = data.walletManager.wallet.publicKey.derivationPath?.rawPath, - ), - ), - ), - ) - } - } - - fun completeTransaction(topic: Topic) { - val activeData = sessions[topic] - val data = activeData?.transactionData ?: return - scope.launch { - val hash = WalletConnectSdkHelper().completeTransaction(data, cardId).guard { - sessions[topic] = activeData.copy(transactionData = null) - store.dispatchOnMain( - WalletConnectAction.RejectRequest( - topic, - data.id, - ), - ) - return@launch - } - acceptRequest(topic, data.id, hash) - sessions[topic] = activeData.copy(transactionData = null) - } - } - - fun signBnb(id: Long, data: ByteArray, topic: Topic) { - val activeData = sessions[topic] ?: return - scope.launch { - val hash = WalletConnectSdkHelper().signBnbTransaction( - data = data, - networkId = activeData.wallet.blockchain?.toNetworkId() ?: "", - derivationPath = activeData.wallet.derivationPath?.rawPath, - cardId = cardId, - ).guard { - store.dispatchOnMain( - WalletConnectAction.RejectRequest( - topic, - id, - ), - ) - return@launch - } - acceptRequest(topic, id, hash) - } - } - - fun handlePersonalSignRequest(message: WCEthereumSignMessage, session: WalletConnectSession, id: Long) { - val activeData = sessions[session.session.topic] ?: return - scope.launch { - val data = WalletConnectSdkHelper().prepareDataForPersonalSign( - message = message.toWcEthereumSignMessage(), - topic = session.session.topic, - id = id, - metaName = session.peerMeta.name, - ) - sessions[session.session.topic] = activeData.copy(personalSignData = data) - store.dispatchOnMain( - GlobalAction.ShowDialog( - WalletConnectDialog.PersonalSign( - WcPreparedRequest.EthSign( - preparedRequestData = data, - topic = session.session.topic, - requestId = id, - derivationPath = session.wallet.derivationPath?.rawPath, - ), - ), - ), - ) - } - } - - fun sendSignedMessage(topic: Topic) { - val activeData = sessions[topic] - val data = activeData?.personalSignData ?: return - scope.launch { - val hash = WalletConnectSdkHelper().signPersonalMessage( - hashToSign = data.hash, - networkId = activeData.wallet.blockchain?.toNetworkId() ?: "", - type = data.type, - derivationPath = activeData.wallet.derivationPath?.rawPath, - cardId = cardId, - ) - .guard { - sessions[topic] = activeData.copy(transactionData = null) - store.dispatchOnMain( - WalletConnectAction.RejectRequest( - topic, - data.id, - ), - ) - return@launch - } - sessions[topic] = activeData.copy(personalSignData = data) - acceptRequest(topic, data.id, hash) - sessions[topic] = activeData.copy(transactionData = null) - } - } - - @Suppress("LongMethod", "ComplexMethod") - private fun setListeners(client: WCClient) { - client.onSessionRequest = { id: Long, peer: WCPeerMeta -> - Timber.d("OnSessionRequest: $peer") - val session = client.session - val data = sessions[session?.topic]?.copy(peerMeta = peer, remotePeerId = client.remotePeerId) - if (data != null && session != null) { - if (!peer.isDappSupported()) { - store.dispatchOnMain( - WalletConnectAction.FailureEstablishingSession( - session = session, - error = TapError.WalletConnect.UnsupportedDapp, - ), - ) - } else { - sessions[session.topic] = data - val sessionData = data.toWalletConnectSession() - sessionData?.let { - store.dispatchOnMain( - WalletConnectAction.ScanCard( - session = sessionData, - chainId = client.chainId?.toIntOrNull(), - ), - ) - } - } - } - } - client.onSessionUpdate = { id: Long, update: WCSessionUpdate -> - Timber.d("onSessionUpdate: $update") - val session = client.session - if (session != null && !update.approved) onSessionClosed(session) - } - client.onEthSendTransaction = { id: Long, transaction: WCEthereumTransaction -> - Timber.d("onEthSendTransaction: $transaction") - // Analytics.logWcEvent( - // AnalyticsAnOld.WcAnalyticsEvent.Action( - // AnalyticsAnOld.WcAction.SendTransaction - // ) - // ) - sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData -> - store.dispatchOnMain( - WalletConnectAction.HandleTransactionRequest( - transaction = transaction, - session = sessionData, - id = id, - type = WcEthTransactionType.EthSendTransaction, - ), - ) - } - } - client.onEthSignTransaction = { id: Long, transaction: WCEthereumTransaction -> - Timber.d("onEthSignTransaction: $transaction") - // Analytics.logWcEvent( - // AnalyticsAnOld.WcAnalyticsEvent.Action( - // AnalyticsAnOld.WcAction.SignTransaction - // ) - // ) - sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData -> - store.dispatchOnMain( - WalletConnectAction.HandleTransactionRequest( - transaction = transaction, - session = sessionData, - id = id, - type = WcEthTransactionType.EthSignTransaction, - ), - ) - } - } - client.onEthSign = { id: Long, message: WCEthereumSignMessage -> - Timber.d("onEthSign: $message") - // Analytics.logWcEvent( - // AnalyticsAnOld.WcAnalyticsEvent.Action( - // AnalyticsAnOld.WcAction.PersonalSign - // ) - // ) - sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData -> - store.dispatchOnMain( - WalletConnectAction.HandlePersonalSignRequest( - message, - sessionData, - id, - ), - ) - } - } - - client.onBnbCancel = { id: Long, order: WCBinanceCancelOrder -> - } - client.onBnbTrade = { id: Long, order: WCBinanceTradeOrder -> - sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData -> - store.dispatchOnMain( - WalletConnectAction.BinanceTransaction.Trade( - id = id, - order = order, - sessionData = sessionData, - ), - ) - } - } - client.onBnbTransfer = { id: Long, order: WCBinanceTransferOrder -> - sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData -> - store.dispatchOnMain( - WalletConnectAction.BinanceTransaction.Transfer( - id = id, - order = order, - sessionData = sessionData, - ), - ) - } - } - client.onBnbTxConfirm = { id: Long, order: WCBinanceTxConfirmParam -> - // send empty approve request if status is OK - if (order.ok) client.approveRequest(id, "") - } - client.onDisconnect = { code: Int, reason: String -> - val session = client.session - if (session != null) { - onSessionClosed(session) - } - } - client.onWalletChangeNetwork = { id: Long, chainId: Int -> - switchChain(chainId, client) - } - client.onWalletAddNetwork = { id: Long, network: WCAddNetwork -> - // TODO - // In fact this method is used to add a EVM network. It provides RPC url and chain ID. - // Here now it just tries to switch to a EVM network with a provided chain ID. - try { - val chainId = Integer.decode(network.chainIdHex) - switchChain(chainId, client) - } catch (exception: Exception) { - Timber.d("WC add network error: chain could not be parsed") - } - } - } - - private fun switchChain(chainId: Int, client: WCClient) { - val blockchain = Blockchain.fromChainId(chainId) - Timber.d("WC switch chainID\nNew Blockchain: $blockchain") - val session = sessions[client.session?.topic]?.toWalletConnectSession() - if (session != null) { - store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session)) - } - } - - companion object { - const val WC_SCHEME = "wc" - - private val tangemPeerMeta = WCPeerMeta(name = "Tangem Wallet", url = "https://tangem.com") - - fun isCorrectWcUri(string: String): Boolean = WCSession.from(string) != null - } -} - -internal typealias Topic = String - -internal data class WalletConnectActiveData( - val peerId: String, - val remotePeerId: String?, - val client: WCClient, - val session: WCSession, - val peerMeta: WCPeerMeta? = null, - val wallet: WalletForSession, - val transactionData: WcTransactionData? = null, - val personalSignData: WcPersonalSignData? = null, -) { - fun toWalletConnectSession(): WalletConnectSession? { - if (peerMeta == null) return null - return WalletConnectSession( - peerId = peerId, - remotePeerId = remotePeerId, - wallet = wallet, - session = session, - peerMeta = peerMeta, - ) - } -} - -@Suppress("MagicNumber") -internal class RetryInterceptor : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val request: Request = chain.request() - val response = chain.proceed(request) - when (response.code) { - 502 -> { - return chain.proceed(request) - } - } - return response - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt deleted file mode 100644 index 19e2e44e42..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.tangem.tap.domain.walletconnect - -import com.tangem.blockchain.common.Blockchain -import com.trustwallet.walletconnect.models.WCPeerMeta - -object WalletConnectNetworkUtils { - fun parseBlockchain(chainId: Int?, peer: WCPeerMeta): Blockchain? { - return when { - peer.url.contains("pancakeswap.finance") -> { - Blockchain.BSC - } - peer.url.contains("optimism") -> { - Blockchain.Optimism - } - chainId != null -> { - Blockchain.fromChainId(chainId) - } - peer.url.contains("matic.network") || peer.name == "Polygon" -> { - Blockchain.Polygon - } - peer.url.contains("binance.org") || peer.name.contains("Binance") -> { - if (peer.icons.firstOrNull()?.contains("testnet") == true) { - Blockchain.BinanceTestnet - } else { - Blockchain.Binance - } - } - peer.name.contains("BSC") -> { - Blockchain.BSC - } - peer.url.contains("honeyswap.1hive.eth.limo") -> { - // Check if something's changed after this bug report: - // https://github.com/1Hive/honeyswap-interface/issues/83 - Blockchain.Gnosis - } - else -> { - Blockchain.Ethereum - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt deleted file mode 100644 index de77928810..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.tangem.tap.domain.walletconnect - -import android.app.Application -import android.content.Context -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.JsonClass -import com.squareup.moshi.Types -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.extensions.toHexString -import com.tangem.datasource.api.common.MoshiConverter -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession -import com.tangem.tap.features.details.redux.walletconnect.WalletForSession -import com.trustwallet.walletconnect.models.WCPeerMeta -import com.trustwallet.walletconnect.models.session.WCSession -import timber.log.Timber -import java.io.FileNotFoundException -import java.nio.charset.Charset - -class WalletConnectRepository(val context: Application) { - private val walletConnectAdapter: JsonAdapter> = MoshiConverter.sdkMoshi.adapter( - Types.newParameterizedType(List::class.java, SessionDao::class.java), - ) - - fun saveSession(session: WalletConnectSession) { - val sessions = loadSavedSessions() + session - saveSessions(sessions) - } - - fun removeSession(session: WCSession) { - val sessions = loadSavedSessions().filterNot { it.session == session } - saveSessions(sessions) - } - - fun loadSavedSessions(): List { - return try { - val json = context.readFileText(FILE_NAME_PREFIX_SESSIONS) - .hexToUtf8() - walletConnectAdapter.fromJson(json)!!.map { it.toSession() } - } catch (e: FileNotFoundException) { - emptyList() - } catch (exception: Exception) { - Timber.w(exception) - emptyList() - } - } - - private fun saveSessions(sessions: List) { - val json = walletConnectAdapter.toJson(sessions.map { SessionDao.fromSession(it) }) - .utf8ToHex() // convert to hex to solve problems with saving text with emojis - Timber.e("WC sessions, saving following json: $json") - context.rewriteFile(json, FILE_NAME_PREFIX_SESSIONS) - } - - private fun String.utf8ToHex(): String { - return this.toByteArray().toHexString() - } - - private fun String.hexToUtf8(): String { - return this.hexToBytes().toString(Charset.defaultCharset()) - } - - private fun Context.readFileText(fileName: String): String = - this.openFileInput(fileName).bufferedReader().readText() - - private fun Context.rewriteFile(content: String, fileName: String) { - this.openFileOutput(fileName, Context.MODE_PRIVATE).use { - it.write(content.toByteArray(), 0, content.length) - } - } - - companion object { - private const val FILE_NAME_PREFIX_SESSIONS = "wc_sessions" - } -} - -@JsonClass(generateAdapter = true) -data class SessionDao( - val peerId: String, - val remotePeerId: String?, - val wallet: WalletForSession, - val session: WCSession, - val peerMeta: WCPeerMeta, -) { - fun toSession(): WalletConnectSession { - return WalletConnectSession( - peerId = peerId, - remotePeerId = remotePeerId, - wallet = wallet, - session = session, - peerMeta = peerMeta, - ) - } - - companion object { - fun fromSession(session: WalletConnectSession): SessionDao { - return SessionDao( - peerId = session.peerId, - remotePeerId = session.remotePeerId, - wallet = session.wallet, - session = session.session, - peerMeta = session.peerMeta, - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index 12dc242d80..015a72b111 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -20,8 +20,6 @@ import com.tangem.operations.sign.SignHashCommand import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.toFormattedString -import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTradeOrder -import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTransferOrder import com.tangem.tap.domain.walletconnect2.domain.TransactionType import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage @@ -227,11 +225,11 @@ class WalletConnectSdkHelper { } fun prepareBnbTradeOrder(data: WcBinanceTradeOrder): BinanceMessageData.Trade { - return BnbHelper.createMessageData(data.toWCBinanceTradeOrder()) + return BnbHelper.createMessageData(data) } fun prepareBnbTransferOrder(data: WcBinanceTransferOrder): BinanceMessageData.Transfer { - return BnbHelper.createMessageData(data.toWCBinanceTransferOrder()) + return BnbHelper.createMessageData(data) } suspend fun signBnbTransaction( diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt deleted file mode 100644 index d227a42923..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.tap.domain.walletconnect.extensions - -import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction -import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage -import com.trustwallet.walletconnect.models.WCPeerMeta -import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage -import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction - -internal fun WCPeerMeta.isDappSupported(): Boolean { - return !unsupportedDappsList.any { this.url.contains(it) } -} - -private val unsupportedDappsList: List = listOf("dydx.exchange") - -internal fun WCEthereumTransaction.toWcEthTransaction(): WcEthereumTransaction { - return WcEthereumTransaction( - from = from, - to = to, - nonce = nonce, - gasPrice = gasPrice, - maxFeePerGas = maxFeePerGas, - maxPriorityFeePerGas = maxPriorityFeePerGas, - gas = gas, - gasLimit = gasLimit, - value = value, - data = data, - ) -} - -internal fun WCEthereumSignMessage.toWcEthereumSignMessage(): WcSignMessage { - return WcSignMessage( - raw = raw, - type = when (type) { - WCEthereumSignMessage.WCSignType.MESSAGE -> WcSignMessage.WCSignType.MESSAGE - WCEthereumSignMessage.WCSignType.PERSONAL_MESSAGE -> WcSignMessage.WCSignType.PERSONAL_MESSAGE - WCEthereumSignMessage.WCSignType.TYPED_MESSAGE -> WcSignMessage.WCSignType.TYPED_MESSAGE - }, - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 47afd1b484..e96974c3b4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -1,118 +1,26 @@ package com.tangem.tap.features.details.redux.walletconnect -import com.tangem.blockchain.common.Blockchain -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.common.redux.NotificationAction -import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.walletconnect.Topic import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen -import com.tangem.wallet.R -import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder -import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder -import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage -import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction -import com.trustwallet.walletconnect.models.session.WCSession import org.rekotlin.Action sealed class WalletConnectAction : Action { - object ResetState : WalletConnectAction() data class HandleDeepLink(val wcUri: String?) : WalletConnectAction() - data class RestoreSessions(val scanResponse: ScanResponse) : WalletConnectAction() + data class StartWalletConnect( val copiedUri: String?, ) : WalletConnectAction() - data class ShowClipboardOrScanQrDialog(val wcUri: String) : WalletConnectAction() - object UnsupportedCard : WalletConnectAction() data class OpenSession( val wcUri: String, ) : WalletConnectAction() - data class SetNewSessionData( - val newSession: NewWcSessionData, - ) : WalletConnectAction() + data class DisconnectSession(val topic: String) : WalletConnectAction() - object RefuseOpeningSession : WalletConnectAction() - data class OpeningSessionTimeout(val session: WCSession) : WalletConnectAction() - data class ScanCard( - val session: WalletConnectSession, - val chainId: Int?, - ) : WalletConnectAction() - - data class ApproveSession( - val session: WCSession, - ) : WalletConnectAction() { - data class Success(val session: WalletConnectSession) : WalletConnectAction() - } - - data class SwitchBlockchain( - val blockchain: Blockchain?, - val session: WalletConnectSession, - ) : WalletConnectAction() - - data class SelectNetwork(val session: WalletConnectSession, val networks: List) : WalletConnectAction() - data class ChooseNetwork(val blockchain: Blockchain) : WalletConnectAction() - data class UpdateBlockchain( - val updatedSession: WalletConnectSession, - ) : WalletConnectAction() - - data class FailureEstablishingSession(val session: WCSession?, val error: TapError? = null) : WalletConnectAction() - data class SetSessionsRestored(val sessions: List) : WalletConnectAction() - - data class DisconnectSession(val topic: String, val session: WCSession?) : WalletConnectAction() - - data class RemoveSession(val session: WCSession) : WalletConnectAction() - - data class HandleTransactionRequest( - val transaction: WCEthereumTransaction, - val session: WalletConnectSession, - val id: Long, - val type: WcEthTransactionType, - ) : - WalletConnectAction() - - data class HandlePersonalSignRequest( - val message: WCEthereumSignMessage, - val session: WalletConnectSession, - val id: Long, - ) : WalletConnectAction() - - data class SendTransaction(val topic: Topic) : WalletConnectAction() - - data class SignMessage(val topic: Topic) : WalletConnectAction() - - data class RejectRequest(val topic: Topic, val id: Long) : WalletConnectAction() - - object NotEnoughFunds : WalletConnectAction(), NotificationAction { - override val messageResource = R.string.wallet_connect_create_tx_not_enough_funds - } - - object NotifyCameraPermissionIsRequired : WalletConnectAction(), NotificationAction { - override val messageResource = R.string.common_camera_denied_alert_message - } - - object BinanceTransaction : WalletConnectAction() { - data class Trade( - val id: Long, - val order: WCBinanceTradeOrder, - val sessionData: WalletConnectSession, - ) : WalletConnectAction() - - data class Transfer( - val id: Long, - val order: WCBinanceTransferOrder, - val sessionData: WalletConnectSession, - ) : WalletConnectAction() - - data class Sign( - val id: Long, - val data: ByteArray, - val topic: Topic, - ) : WalletConnectAction() - } + data class RejectRequest(val topic: String, val id: Long) : WalletConnectAction() + data class ShowClipboardOrScanQrDialog(val wcUri: String) : WalletConnectAction() //region WalletConnect 2.0 object ApproveProposal : WalletConnectAction() object RejectProposal : WalletConnectAction() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 4a26922744..9dbbdbba5d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -1,44 +1,28 @@ package com.tangem.tap.features.details.redux.walletconnect import androidx.core.os.bundleOf -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchain.common.derivation.DerivationStyle -import com.tangem.common.extensions.guard import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction -import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.common.util.derivationStyleProvider -import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.qrscanning.models.SourceType -import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.walletconnect.WalletConnectActions import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.walletconnect.BnbHelper -import com.tangem.tap.domain.walletconnect.WalletConnectManager -import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils -import com.tangem.tap.domain.walletconnect.extensions.toWcEthTransaction import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest import com.tangem.tap.domain.walletconnect2.domain.models.Account -import com.tangem.tap.domain.walletconnect2.domain.models.BnbData import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.userWalletsListManager -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware @@ -46,7 +30,6 @@ import timber.log.Timber @Suppress("LargeClass") class WalletConnectMiddleware { - private var walletConnectManager = WalletConnectManager() private val walletConnectInteractor: WalletConnectInteractor get() = store.inject(DaggerGraphState::walletConnectInteractor) private val walletConnectRepository: WalletConnectRepository @@ -88,18 +71,18 @@ class WalletConnectMiddleware { wcInteractor.setUserChains(getAccountsForWc(wcInteractor, userWallet)) } } - is WalletConnectAction.ResetState -> walletConnectManager = WalletConnectManager() - is WalletConnectAction.RestoreSessions -> { - walletConnectManager.restoreSessions(action.scanResponse) - } is WalletConnectAction.HandleDeepLink -> { if (!action.wcUri.isNullOrBlank()) { store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri)) } } + is WalletConnectAction.DisconnectSession -> { + walletConnectInteractor.disconnectSession(action.topic) + } is WalletConnectAction.StartWalletConnect -> { val uri = action.copiedUri if (uri != null && isWalletConnectUri(uri)) { + // TODO check store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri)) } else { store.dispatchOnMain( @@ -112,27 +95,6 @@ class WalletConnectMiddleware { ) } } - is WalletConnectAction.SelectNetwork -> { - store.dispatch( - GlobalAction.ShowDialog( - WalletConnectDialog.ChooseNetwork( - session = action.session, - networks = action.networks, - ), - ), - ) - } - is WalletConnectAction.ChooseNetwork -> { - val data = state()?.walletConnectState?.newSessionData ?: return - scope.launch { - prepareWalletManager( - scanResponse = data.scanResponse, - blockchain = action.blockchain, - session = data.session, - walletConnectManager = walletConnectManager, - ) - } - } is WalletConnectAction.ShowClipboardOrScanQrDialog -> { store.dispatchOnMain( GlobalAction.ShowDialog( @@ -142,176 +104,15 @@ class WalletConnectMiddleware { ), ) } - is WalletConnectAction.OpeningSessionTimeout -> { - Timber.e("OpeningSessionTimeout for topic ${action.session.topic}") - // do not show dialog for now, it shows always to user if cannot establish connection - // store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout)) - } - is WalletConnectAction.FailureEstablishingSession -> { - Timber.e("FailureEstablishingSession for topic ${action.session?.topic}") - // disable alerts in release to avoid annoying users - // if (action.error != null) { - // store.dispatch( - // GlobalAction.ShowDialog( - // AppDialog.SimpleOkDialogRes( - // headerId = R.string.common_warning, - // messageId = action.error.messageResource, - // ), - // ), - // ) - // } - if (action.session != null) { - walletConnectManager.disconnect(action.session) - } - } - is WalletConnectAction.UnsupportedCard -> { - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedCard)) - } is WalletConnectAction.OpenSession -> { val index = action.wcUri.indexOf("@") when (action.wcUri[index + 1]) { - '1' -> { - walletConnectManager.connect(wcUri = action.wcUri) - } '2' -> walletConnectRepository.pair(uri = action.wcUri) } } - is WalletConnectAction.RefuseOpeningSession -> { - Timber.e("RefuseOpeningSession") - - // do not show for now to avoid anoying users with alert - // store.dispatch( - // GlobalAction.ShowDialog( - // WalletConnectDialog.OpeningSessionRejected, - // ), - // ) - } - is WalletConnectAction.ScanCard -> { - val scanResponse = userWalletsListManager.selectedUserWalletSync.guard { - Timber.w("Unable to get selected user wallet for WC session") - return - } - - scope.launch(Dispatchers.Main) { - scanCard(scanResponse, action.session, action.chainId) - } - } - is WalletConnectAction.ApproveSession -> { - walletConnectManager.approve(action.session) - } - is WalletConnectAction.DisconnectSession -> { - if (action.session != null) { - walletConnectManager.disconnect(action.session) - } else { - walletConnectInteractor.disconnectSession(action.topic) - } - } - is WalletConnectAction.HandleTransactionRequest -> { - walletConnectManager.handleTransactionRequest( - transaction = action.transaction.toWcEthTransaction(), - session = action.session, - id = action.id, - type = action.type, - ) - } - is WalletConnectAction.HandlePersonalSignRequest -> { - walletConnectManager.handlePersonalSignRequest( - message = action.message, - session = action.session, - id = action.id, - ) - } is WalletConnectAction.RejectRequest -> { - walletConnectManager.rejectRequest(action.topic, action.id) walletConnectInteractor.cancelRequest(action.topic, action.id) } - is WalletConnectAction.SendTransaction -> { - walletConnectManager.completeTransaction(action.topic) - } - is WalletConnectAction.SignMessage -> { - walletConnectManager.sendSignedMessage(action.topic) - } - is WalletConnectAction.BinanceTransaction.Trade -> { - val messageData = BnbHelper.createMessageData(action.order) - store.dispatchOnMain( - GlobalAction.ShowDialog( - WalletConnectDialog.BnbTransactionDialog( - WcPreparedRequest.BnbTransaction( - preparedRequestData = BnbData( - data = messageData, - topic = action.sessionData.session.topic, - requestId = action.id, - dAppName = action.sessionData.peerMeta.name, - ), - topic = action.sessionData.session.topic, - requestId = action.id, - derivationPath = action.sessionData.wallet.derivationPath?.rawPath, - ), - ), - ), - ) - } - is WalletConnectAction.BinanceTransaction.Transfer -> { - val messageData = BnbHelper.createMessageData(action.order) - store.dispatchOnMain( - GlobalAction.ShowDialog( - WalletConnectDialog.BnbTransactionDialog( - WcPreparedRequest.BnbTransaction( - preparedRequestData = BnbData( - data = messageData, - topic = action.sessionData.session.topic, - requestId = action.id, - dAppName = action.sessionData.peerMeta.name, - ), - topic = action.sessionData.session.topic, - requestId = action.id, - derivationPath = action.sessionData.wallet.derivationPath?.rawPath, - ), - ), - ), - ) - } - is WalletConnectAction.BinanceTransaction.Sign -> { - walletConnectManager.signBnb( - id = action.id, - data = action.data, - topic = action.topic, - ) - } - is WalletConnectAction.SwitchBlockchain -> { - if (action.session.wallet.derivationStyle == DerivationStyle.LEGACY) { - Timber.d("Cannot switch chains on AC01/AC02 wallets") - return - } - val blockchain = action.blockchain.guard { - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork())) - return - } - scope.launch { - val walletManager = getWalletManager( - wallet = action.session.wallet, - blockchain = blockchain, - ).guard { - store.dispatchOnMain( - GlobalAction.ShowDialog( - WalletConnectDialog.AddNetwork(listOf(blockchain.fullName)), - ), - ) - return@launch - } - val updatedWallet = action.session.wallet.copy( - walletPublicKey = walletManager.wallet.publicKey.seedKey, - derivedPublicKey = walletManager.wallet.publicKey.derivedKey, - derivationPath = walletManager.wallet.publicKey.derivationPath, - blockchain = action.blockchain, - ) - val updatedSession = action.session.copy(wallet = updatedWallet) - store.dispatchOnMain(WalletConnectAction.UpdateBlockchain(updatedSession)) - } - } - is WalletConnectAction.UpdateBlockchain -> { - walletConnectManager.updateBlockchain(action.updatedSession) - } is WalletConnectAction.ApproveProposal -> { scope.launch { val accounts = getWalletManagers() @@ -402,119 +203,8 @@ class WalletConnectMiddleware { return walletManagerFacade.getStoredWalletManagers(userWallet.walletId) } - private suspend fun scanCard(userWallet: UserWallet, session: WalletConnectSession, chainId: Int?) { - val blockchain = WalletConnectNetworkUtils.parseBlockchain( - chainId = chainId, - peer = session.peerMeta, - ).guard { - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork())) - return - } - - handleScanResponse(userWallet, session, blockchain) - } - - private suspend fun getAvailableEvmBlockchains(userWalletId: UserWalletId): List { - val currenciesRepository = store.inject(DaggerGraphState::currenciesRepository) - - return currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId) - .asSequence() - .filterIsInstance() - .filterNot { it.isCustom } - .mapNotNull { Blockchain.fromNetworkId(it.network.id.value) } - .filter { it.isEvm() } - .toList() - } - - private suspend fun prepareWalletManager( - scanResponse: ScanResponse, - blockchain: Blockchain, - session: WalletConnectSession, - walletConnectManager: WalletConnectManager, - ) { - val walletManager = getWalletManager(session.wallet, blockchain).guard { - store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(session.session)) - store.dispatchOnMain( - GlobalAction.ShowDialog( - WalletConnectDialog.AddNetwork(listOf(blockchain.fullName)), - ), - ) - return - } - val wallet = walletManager.wallet - val derivedKey = - if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) { - null - } else { - walletManager.wallet.publicKey.blockchainKey - } - val walletForSession = WalletForSession( - walletPublicKey = wallet.publicKey.seedKey, - derivedPublicKey = derivedKey, - derivationPath = wallet.publicKey.derivationPath, - derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle(), - blockchain = wallet.blockchain, - ) - - withMainContext { - val updatedSession = session.copy(wallet = walletForSession) - walletConnectManager.updateSession(updatedSession) - - store.dispatch(WalletConnectAction.ApproveSession(session.session)) - } - } - - private suspend fun handleScanResponse( - userWallet: UserWallet, - session: WalletConnectSession, - blockchain: Blockchain, - ) { - val scanResponse = userWallet.scanResponse - - if (!scanResponse.cardTypesResolver.isMultiwalletAllowed()) { - store.dispatchOnMain(WalletConnectAction.UnsupportedCard) - return - } - val updatedSession = session.copy(wallet = session.wallet.copy(blockchain = blockchain)) - store.dispatch( - WalletConnectAction.SetNewSessionData( - NewWcSessionData(updatedSession, scanResponse, blockchain), - ), - ) - val blockchains = if (blockchain.isEvm()) { - getAvailableEvmBlockchains(userWallet.walletId) - } else { - emptyList() - } - store.dispatch( - GlobalAction.ShowDialog( - WalletConnectDialog.ApproveWcSession(session = updatedSession, networks = blockchains), - ), - ) - } - - private suspend fun getWalletManager(wallet: WalletForSession, blockchain: Blockchain): WalletManager? { - val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) { - Blockchain.EthereumTestnet - } else { - blockchain - } - val userWallet = userWalletsListManager.selectedUserWalletSync ?: return null - val derivation = blockchainToMake.derivationPath( - style = userWallet.scanResponse.derivationStyleProvider.getDerivationStyle(), - )?.rawPath - - val walletManagerFacade = store.inject(DaggerGraphState::walletManagersFacade) - - return walletManagerFacade.getOrCreateWalletManager( - userWalletId = userWallet.walletId, - blockchain = blockchainToMake, - derivationPath = derivation, - ) - } - private fun isWalletConnectUri(uri: String): Boolean { - return WalletConnectManager.isCorrectWcUri(uri) || walletConnectInteractor.isWalletConnectUri(uri) + return walletConnectInteractor.isWalletConnectUri(uri) } private suspend fun getAccountsForWc(wcInteractor: WalletConnectInteractor, userWallet: UserWallet): List { diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt index 6840f26c63..20217386f2 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt @@ -7,36 +7,9 @@ object WalletConnectReducer { if (action !is WalletConnectAction) return state return when (action) { - is WalletConnectAction.ResetState -> return WalletConnectState() - is WalletConnectAction.ApproveSession.Success -> { - state.copy( - loading = false, - sessions = state.sessions + action.session, - ) - } is WalletConnectAction.OpenSession -> { state.copy(loading = true) } - is WalletConnectAction.SetNewSessionData -> { - state.copy(newSessionData = action.newSession) - } - is WalletConnectAction.SetSessionsRestored -> state.copy( - sessions = action.sessions, - ) - is WalletConnectAction.RemoveSession -> { - val sessions = - state.sessions.filterNot { it.session.toUri() == action.session.toUri() } - state.copy(sessions = sessions) - } - is WalletConnectAction.UnsupportedCard, - is WalletConnectAction.RefuseOpeningSession, - is WalletConnectAction.OpeningSessionTimeout, - is WalletConnectAction.FailureEstablishingSession, - -> state.copy(loading = false) - is WalletConnectAction.UpdateBlockchain -> state.copy( - sessions = state.sessions - .filterNot { it.peerId == action.updatedSession.peerId } + action.updatedSession, - ) is WalletConnectAction.ApproveProposal -> state.copy(loading = true) is WalletConnectAction.RejectProposal, is WalletConnectAction.SessionEstablished, diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt index 68cfd4255b..750f4b04c5 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt @@ -14,12 +14,9 @@ import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData -import com.trustwallet.walletconnect.models.WCPeerMeta -import com.trustwallet.walletconnect.models.session.WCSession data class WalletConnectState( val loading: Boolean = false, - val sessions: List = listOf(), val wc2Sessions: List = listOf(), val newSessionData: NewWcSessionData? = null, ) @@ -34,8 +31,6 @@ data class WalletConnectSession( val peerId: String, val remotePeerId: String?, val wallet: WalletForSession, - val session: WCSession, - val peerMeta: WCPeerMeta, ) { fun getAddress(): String? { val key = wallet.derivedPublicKey ?: wallet.walletPublicKey ?: return null diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt index 1733d8d946..8528a2ebe0 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt @@ -11,7 +11,6 @@ import com.tangem.core.navigation.NavigationAction import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.common.analytics.events.WalletConnect -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState import com.tangem.tap.store import dagger.hilt.android.AndroidEntryPoint @@ -42,13 +41,6 @@ internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber WcSessionForScreen.fromSession(wcSession) } + state.wc2Sessions + val sessions = state.wc2Sessions return WalletConnectScreenState( sessions.toImmutableList(), isLoading = state.loading, - onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, state.sessions, state.wc2Sessions) }, + onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, sessions) }, onAddSession = { copiedUri -> store.dispatch(WalletConnectAction.StartWalletConnect(copiedUri)) }, ) } - private fun onRemoveSession( - sessionUri: String, - sessions: List, - wc2sessions: List, - ) { - sessions - .firstOrNull { it.session.toUri() == sessionUri } - ?.let { baseSession -> - store.dispatch(WalletConnectAction.DisconnectSession(baseSession.session.topic, baseSession.session)) - return - } + private fun onRemoveSession(sessionUri: String, wc2sessions: List) { wc2sessions.firstOrNull { it.sessionId == sessionUri }?.let { - store.dispatch(WalletConnectAction.DisconnectSession(sessionUri, null)) + store.dispatch(WalletConnectAction.DisconnectSession(sessionUri)) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt deleted file mode 100644 index d49a684033..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.tangem.tap.features.details.ui.walletconnect.dialogs - -import android.content.Context -import androidx.appcompat.app.AlertDialog -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.tangem.blockchain.common.Blockchain -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession -import com.tangem.tap.store -import com.tangem.wallet.R - -object ApproveWcSessionDialog { - fun create(session: WalletConnectSession, networks: List, context: Context): AlertDialog { - val sessionBlockchain = requireNotNull(session.wallet.blockchain) { "session network is null" } - val message = context.getString( - R.string.wallet_connect_request_session_start, - session.peerMeta.name, - sessionBlockchain.fullName, - session.peerMeta.url, - ) - return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply { - setTitle(context.getString(R.string.wallet_connect_title)) - setMessage(message) - setPositiveButton(context.getText(R.string.common_start)) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - store.dispatch(WalletConnectAction.ChooseNetwork(sessionBlockchain)) - } - if (networks.size > 1) { - setNeutralButton(context.getText(R.string.wallet_connect_select_network)) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - store.dispatch(WalletConnectAction.SelectNetwork(session = session, networks = networks)) - } - } - setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session)) - } - setOnCancelListener { - store.dispatch(GlobalAction.HideDialog) - store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session)) - } - }.create() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt index cddaab442a..aee88834e4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt @@ -42,13 +42,6 @@ object BnbTransactionDialog { setTitle(context.getString(R.string.wallet_connect_title)) setMessage(fullMessage) setPositiveButton(positiveButtonTitle) { _, _ -> - store.dispatch( - WalletConnectAction.BinanceTransaction.Sign( - id = preparedData.requestId, - data = data.data, - topic = preparedData.topic, - ), - ) store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData)) } setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ChooseNetworkDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ChooseNetworkDialog.kt deleted file mode 100644 index b99df2b02e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ChooseNetworkDialog.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.tangem.tap.features.details.ui.walletconnect.dialogs - -import android.content.Context -import androidx.appcompat.app.AlertDialog -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.tangem.blockchain.common.Blockchain -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession -import com.tangem.tap.store -import com.tangem.wallet.R - -object ChooseNetworkDialog { - fun create(session: WalletConnectSession, networks: List, context: Context): AlertDialog { - return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog) - .setTitle(context.getString(R.string.wallet_connect_select_network)) - .setNegativeButton(context.getString(R.string.common_cancel)) { _, _ -> - store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session)) - } - .setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - .setSingleChoiceItems(networks.map { it.fullName }.toTypedArray(), 0) { _, which -> - networks.getOrNull(which)?.let { selectedBlockchain -> - store.dispatch( - WalletConnectAction.ChooseNetwork( - blockchain = selectedBlockchain, - ), - ) - store.dispatch(GlobalAction.HideDialog) - } - } - .create() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt index 4230afdeaf..4403b6d4a6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt @@ -17,7 +17,6 @@ object PersonalSignDialog { setTitle(context.getString(R.string.wallet_connect_title)) setMessage(message) setPositiveButton(context.getText(R.string.common_sign)) { _, _ -> - store.dispatch(WalletConnectAction.SignMessage(data.topic)) store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData)) } setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt index 608eebc130..3ec2d8bfda 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt @@ -32,11 +32,9 @@ object TransactionDialog { setMessage(message) setPositiveButton(positiveButtonTitle) { _, _ -> if (data.isEnoughFundsToSend) { - store.dispatch(WalletConnectAction.SendTransaction(data.topic)) store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData)) } else { store.dispatch(WalletConnectAction.RejectRequest(data.topic, data.id)) - store.dispatch(WalletConnectAction.NotEnoughFunds) } } setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> diff --git a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/WalletConnectLinkIntentHandler.kt b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/WalletConnectLinkIntentHandler.kt index 23f8ec2235..46f7d2d07d 100644 --- a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/WalletConnectLinkIntentHandler.kt +++ b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/WalletConnectLinkIntentHandler.kt @@ -3,7 +3,6 @@ package com.tangem.tap.features.intentHandler.handlers import android.content.Intent import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.removePrefixOrNull -import com.tangem.tap.domain.walletconnect.WalletConnectManager import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.intentHandler.IntentHandler import com.tangem.tap.store @@ -20,7 +19,7 @@ class WalletConnectLinkIntentHandler : IntentHandler { val scheme = intent.scheme ?: return false val wcUri = when (scheme) { - WalletConnectManager.WC_SCHEME -> intentData.toString() + WC_SCHEME -> intentData.toString() TANGEM_SCHEME -> intentData.toString().removePrefixOrNull(TANGEM_WC_PREFIX) else -> null } @@ -40,8 +39,9 @@ class WalletConnectLinkIntentHandler : IntentHandler { } private companion object { - private const val TANGEM_SCHEME = "tangem" - private const val TANGEM_WC_PREFIX = "tangem://wc?uri=" - private const val DEFAULT_CHARSET_NAME = "UTF-8" + const val TANGEM_SCHEME = "tangem" + const val TANGEM_WC_PREFIX = "tangem://wc?uri=" + const val DEFAULT_CHARSET_NAME = "UTF-8" + const val WC_SCHEME = "wc" } } \ No newline at end of file From 625277f5185191475736749c36d5d7f7297c4e10 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Apr 2024 12:42:26 +0400 Subject: [PATCH 14/98] Updated on 2026-08-14 --- .../repository/DefaultCurrenciesRepository.kt | 52 ++++++++++++++-- .../repository/DefaultNetworksRepository.kt | 59 +++++++++++++++---- domain/tokens/build.gradle.kts | 2 +- .../tokens/repository/CurrenciesRepository.kt | 37 ++++++++---- .../tokens/repository/NetworksRepository.kt | 15 +++++ .../repository/MockCurrenciesRepository.kt | 8 +++ .../repository/MockNetworksRepository.kt | 9 +++ 7 files changed, 152 insertions(+), 30 deletions(-) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 6074c94609..9a749d8a1d 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -1,5 +1,6 @@ package com.tangem.data.tokens.repository +import arrow.core.raise.catch import com.tangem.blockchain.common.Blockchain import com.tangem.data.common.api.safeApiCall import com.tangem.data.common.cache.CacheRegistry @@ -20,6 +21,9 @@ import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.common.util.hasDerivation import com.tangem.domain.core.error.DataError +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.core.lce.lceFlow +import com.tangem.domain.core.utils.lceError import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -55,6 +59,10 @@ internal class DefaultCurrenciesRepository( private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility() private val customTokensMerger = CustomTokensMerger(tangemTechApi, dispatchers) + private val isMultiCurrencyWalletCurrenciesFetching = MutableStateFlow( + value = emptyMap(), + ) + override suspend fun saveTokens( userWalletId: UserWalletId, currencies: List, @@ -215,6 +223,30 @@ internal class DefaultCurrenciesRepository( .cancellable() } + override fun getMultiCurrencyWalletCurrenciesUpdatesLce( + userWalletId: UserWalletId, + ): LceFlow> = lceFlow { + val userWallet = getUserWallet(userWalletId) + catch({ ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true) }) { + raise(it.lceError()) + } + + launch(dispatchers.io) { + combine( + getMultiCurrencyWalletCurrencies(userWallet), + isMultiCurrencyWalletCurrenciesFetching.map { it.getOrElse(userWallet.walletId) { false } }, + ) { currencies, isFetching -> + send(currencies, isStillLoading = isFetching) + }.collect() + } + + withContext(dispatchers.io) { + catch({ fetchTokensIfCacheExpired(userWallet, refresh = false) }) { + raise(it.lceError()) + } + } + } + override suspend fun getMultiCurrencyWalletCurrenciesSync( userWalletId: UserWalletId, refresh: Boolean, @@ -369,11 +401,21 @@ internal class DefaultCurrenciesRepository( } private suspend fun fetchTokensIfCacheExpired(userWallet: UserWallet, refresh: Boolean) { - cacheRegistry.invokeOnExpire( - key = getTokensCacheKey(userWallet.walletId), - skipCache = refresh, - block = { fetchTokens(userWallet) }, - ) + try { + isMultiCurrencyWalletCurrenciesFetching.update { + it + (userWallet.walletId to true) + } + + cacheRegistry.invokeOnExpire( + key = getTokensCacheKey(userWallet.walletId), + skipCache = refresh, + block = { fetchTokens(userWallet) }, + ) + } finally { + isMultiCurrencyWalletCurrenciesFetching.update { + it - userWallet.walletId + } + } } private suspend fun fetchTokens(userWallet: UserWallet) { diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index 44096bdcd7..b8e9d5c47c 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -1,5 +1,6 @@ package com.tangem.data.tokens.repository +import arrow.core.raise.catch import com.tangem.blockchain.common.Blockchain import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory @@ -10,6 +11,9 @@ import com.tangem.datasource.local.token.UserTokensStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.core.lce.lceFlow +import com.tangem.domain.core.utils.lceError import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network @@ -20,10 +24,7 @@ import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.* -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.cancellable -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.* import timber.log.Timber internal class DefaultNetworksRepository( @@ -40,6 +41,10 @@ internal class DefaultNetworksRepository( private val responseCurrenciesFactory by lazy { ResponseCryptoCurrenciesFactory() } private val networkStatusFactory by lazy { NetworkStatusFactory() } + private val isNetworkStatusesFetching = MutableStateFlow( + value = emptyMap(), + ) + override fun getNetworkStatusesUpdates( userWalletId: UserWalletId, networks: Set, @@ -55,6 +60,26 @@ internal class DefaultNetworksRepository( } .cancellable() + override fun getNetworkStatusesUpdatesLce( + userWalletId: UserWalletId, + networks: Set, + ): LceFlow> = lceFlow { + launch(dispatchers.io) { + combine( + networksStatusesStore.get(userWalletId), + isNetworkStatusesFetching.map { it.getOrElse(userWalletId) { false } }, + ) { statuses, isFetching -> + send(statuses, isStillLoading = isFetching) + }.collect() + } + + withContext(dispatchers.io) { + catch({ fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh = false) }) { + raise(it.lceError()) + } + } + } + override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set) { val currencies = getCurrencies(userWalletId, networks) withContext(dispatchers.io) { @@ -81,15 +106,25 @@ internal class DefaultNetworksRepository( networks: Set, refresh: Boolean, ) { - val currencies = getCurrencies(userWalletId, networks) - coroutineScope { - networks - .map { network -> - async { - fetchNetworkStatusIfCacheExpired(userWalletId, network, currencies, refresh) + try { + isNetworkStatusesFetching.update { + it + (userWalletId to true) + } + + val currencies = getCurrencies(userWalletId, networks) + coroutineScope { + networks + .map { network -> + async { + fetchNetworkStatusIfCacheExpired(userWalletId, network, currencies, refresh) + } } - } - .awaitAll() + .awaitAll() + } + } finally { + isNetworkStatusesFetching.update { + it - userWalletId + } } } diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts index 5d91ab4124..40f87abfa7 100644 --- a/domain/tokens/build.gradle.kts +++ b/domain/tokens/build.gradle.kts @@ -11,7 +11,7 @@ android { dependencies { /** Project - Domain */ - implementation(projects.domain.core) + api(projects.domain.core) implementation(projects.domain.models) implementation(projects.domain.legacy) implementation(projects.domain.tokens.models) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt index 60f8d5da7e..61cc6f66ee 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt @@ -1,5 +1,7 @@ package com.tangem.domain.tokens.repository +import com.tangem.domain.core.error.DataError +import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.FeePaidCurrency @@ -20,7 +22,7 @@ interface CurrenciesRepository { * @param currencies The list of cryptocurrencies to be saved. * @param isGroupedByNetwork A boolean flag indicating whether the tokens should be grouped by network. * @param isSortedByBalance A boolean flag indicating whether the tokens should be sorted by balance. - * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet + * @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet * ID provided. */ suspend fun saveTokens( @@ -35,7 +37,7 @@ interface CurrenciesRepository { * * @param userWalletId The unique identifier of the user wallet. * @param currencies The currencies which must be added. - * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet + * @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet * ID provided. */ suspend fun addCurrencies(userWalletId: UserWalletId, currencies: List) @@ -45,7 +47,7 @@ interface CurrenciesRepository { * * @param userWalletId The unique identifier of the user wallet. * @param currency The currency which must be removed. - * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If multi-currency user wallet + * @throws DataError.UserWalletError.WrongUserWallet If multi-currency user wallet * ID provided. */ suspend fun removeCurrency(userWalletId: UserWalletId, currency: CryptoCurrency) @@ -55,7 +57,7 @@ interface CurrenciesRepository { * * @param userWalletId The unique identifier of the user wallet. * @param currencies The currencies which must be removed. - * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet + * @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet * ID provided. */ suspend fun removeCurrencies(userWalletId: UserWalletId, currencies: List) @@ -65,7 +67,7 @@ interface CurrenciesRepository { * * @param userWalletId The unique identifier of the user wallet. * @return The primary cryptocurrency associated with the user wallet. - * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If multi-currency user wallet + * @throws DataError.UserWalletError.WrongUserWallet If multi-currency user wallet * ID provided. */ suspend fun getSingleCurrencyWalletPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency @@ -75,7 +77,7 @@ interface CurrenciesRepository { * * @param userWalletId The unique identifier of the user wallet. * @return The primary cryptocurrency associated with the user wallet. - * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If multi-currency user wallet + * @throws DataError.UserWalletError.WrongUserWallet If multi-currency user wallet * ID provided. */ suspend fun getSingleCurrencyWalletWithCardCurrencies(userWalletId: UserWalletId): List @@ -87,7 +89,7 @@ interface CurrenciesRepository { * @param userWalletId The unique identifier of the user wallet. * @param id The unique identifier of the cryptocurrency to be retrieved. * @return The cryptocurrency associated with the user wallet and ID. - * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet + * @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet * ID provided. */ suspend fun getSingleCurrencyWalletWithCardCurrency( @@ -102,11 +104,22 @@ interface CurrenciesRepository { * * @param userWalletId The unique identifier of the user wallet. * @return A [Flow] emitting the set of cryptocurrencies associated with the user wallet. - * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet + * @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet * ID provided. */ fun getMultiCurrencyWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow> + /** + * Retrieves updates of the list of cryptocurrencies within a multi-currency wallet. + * + * Loads remote cryptocurrencies if they have expired. + * + * @param userWalletId The unique identifier of the user wallet. + * @return A [LceFlow] emitting the set of cryptocurrencies associated with the user wallet. May emit an + * [DataError.UserWalletError.WrongUserWallet] if single-currency user wallet ID provided. + */ + fun getMultiCurrencyWalletCurrenciesUpdatesLce(userWalletId: UserWalletId): LceFlow> + /** * Retrieves the list of cryptocurrencies within a multi-currency wallet. * @@ -115,7 +128,7 @@ interface CurrenciesRepository { * @param userWalletId The unique identifier of the user wallet. * @param refresh A boolean flag indicating whether the data should be refreshed. * @return A list of [CryptoCurrency]. - * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet + * @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet * ID provided. */ suspend fun getMultiCurrencyWalletCurrenciesSync( @@ -129,7 +142,7 @@ interface CurrenciesRepository { * @param userWalletId The unique identifier of the user wallet. * @param id The unique identifier of the cryptocurrency to be retrieved. * @return The cryptocurrency associated with the user wallet and ID. - * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet + * @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet * ID provided. */ suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: CryptoCurrency.ID): CryptoCurrency @@ -152,7 +165,7 @@ interface CurrenciesRepository { * * @param userWalletId The unique identifier of the user wallet. * @return A [Flow] emitting a boolean value indicating whether the tokens are grouped. - * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet + * @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet * ID provided. */ fun isTokensGrouped(userWalletId: UserWalletId): Flow @@ -162,7 +175,7 @@ interface CurrenciesRepository { * * @param userWalletId The unique identifier of the user wallet. * @return A [Flow] emitting a boolean value indicating whether the tokens are sorted by balance. - * @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet + * @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet * ID provided. */ fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt index 6ec3fbb11a..63c61d02bd 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens.repository +import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.wallets.models.UserWalletId @@ -20,6 +21,20 @@ interface NetworksRepository { */ fun getNetworkStatusesUpdates(userWalletId: UserWalletId, networks: Set): Flow> + /** + * Retrieves updates of network statuses of specified blockchain networks for a specific user wallet. + * + * Loads remote network statuses if they have expired. + * + * @param userWalletId The unique identifier of the user wallet. + * @param networks A set of network which statuses are to be retrieved. + * @return A [LceFlow] emitting a set of [NetworkStatus] objects corresponding to the specified networks. + */ + fun getNetworkStatusesUpdatesLce( + userWalletId: UserWalletId, + networks: Set, + ): LceFlow> + /** * Fetches pending transactions for given network * diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt index 6fb77065d5..e3233d8474 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt @@ -3,6 +3,8 @@ package com.tangem.domain.tokens.repository import arrow.core.Either import arrow.core.getOrElse import com.tangem.domain.core.error.DataError +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.core.utils.toLce import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.FeePaidCurrency @@ -78,6 +80,12 @@ internal class MockCurrenciesRepository( return tokens.map { it.getOrElse { e -> throw e } } } + override fun getMultiCurrencyWalletCurrenciesUpdatesLce( + userWalletId: UserWalletId, + ): LceFlow> { + return tokens.map { it.toLce() } + } + override suspend fun getMultiCurrencyWalletCurrency( userWalletId: UserWalletId, id: CryptoCurrency.ID, diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt index ecf1d2a23b..9cf17f4e9e 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt @@ -3,6 +3,8 @@ package com.tangem.domain.tokens.repository import arrow.core.Either import arrow.core.getOrElse import com.tangem.domain.core.error.DataError +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.core.utils.toLce import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.wallets.models.UserWalletId @@ -21,6 +23,13 @@ internal class MockNetworksRepository( return statuses.map { it.getOrElse { e -> throw e } } } + override fun getNetworkStatusesUpdatesLce( + userWalletId: UserWalletId, + networks: Set, + ): LceFlow> { + return statuses.map { it.toLce() } + } + override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set) { // no-op } From d32b504a80e718a92d00ded55e3d12ae61b58db1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Apr 2024 08:59:52 +0200 Subject: [PATCH 15/98] Updated on 2026-08-14 --- app/build.gradle.kts | 2 + .../ApplicationInjectionExecutionRule.kt | 28 ++++ .../kotlin/com/tangem/common/BaseTestCase.kt | 56 ++++++++ .../com/tangem/common/HiltTestRunner.kt | 17 +++ .../tangem/common/TangemEmptyApplication.kt | 11 ++ .../common/di/TangemMockedApplication.kt | 7 + .../kotlin/com/tangem/common/di/TestModule.kt | 28 ++++ .../tangem/helpers/base/BaseAutoTestCase.kt | 27 ---- .../kotlin/com/tangem/tests/StoriesTest.kt | 56 ++++---- app/src/main/AndroidManifest.xml | 2 +- .../com/tangem/tap/ApplicationEntryPoint.kt | 98 +++++++++++++ .../main/java/com/tangem/tap/MainActivity.kt | 6 +- ...TapApplication.kt => TangemApplication.kt} | 133 +++++++++--------- .../com/tangem/tap/TangemHiltApplication.kt | 6 + .../java/com/tangem/tap/di/ActivityModule.kt | 20 +-- .../tangem/tap/di/TangemSdkManagerModule.kt | 32 +++++ .../com/tangem/tap/di/data/CardDataModule.kt | 2 +- .../tap/di/domain/SettingsDomainModule.kt | 2 +- .../card/DefaultDerivationsRepository.kt | 2 +- .../repository/DefaultScanCardRepository.kt | 2 +- .../tap/domain/{ => sdk}/TangemSdkManager.kt | 2 +- .../{ => sdk/impl}/DefaultTangemSdkManager.kt | 3 +- .../{ => sdk/impl}/MockTangemSdkManager.kt | 3 +- .../DefaultLegacySettingsRepository.kt | 2 +- .../com/tangem/tap/proxy/AppStateHolder.kt | 2 +- .../card/DefaultDerivationsRepositoryTest.kt | 2 +- gradle/dependencies.toml | 2 + .../extension/AppExtensionConfigurations.kt | 2 +- 28 files changed, 400 insertions(+), 155 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/tangem/common/ApplicationInjectionExecutionRule.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/common/HiltTestRunner.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/common/TangemEmptyApplication.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/common/di/TangemMockedApplication.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/common/di/TestModule.kt delete mode 100644 app/src/androidTest/kotlin/com/tangem/helpers/base/BaseAutoTestCase.kt create mode 100644 app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt rename app/src/main/java/com/tangem/tap/{TapApplication.kt => TangemApplication.kt} (75%) create mode 100644 app/src/main/java/com/tangem/tap/TangemHiltApplication.kt create mode 100644 app/src/main/java/com/tangem/tap/di/TangemSdkManagerModule.kt rename app/src/main/java/com/tangem/tap/domain/{ => sdk}/TangemSdkManager.kt (98%) rename app/src/main/java/com/tangem/tap/domain/{ => sdk/impl}/DefaultTangemSdkManager.kt (99%) rename app/src/main/java/com/tangem/tap/domain/{ => sdk/impl}/MockTangemSdkManager.kt (97%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 16b9ab3d1d..998c7c7546 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -215,6 +215,8 @@ dependencies { androidTestImplementation(deps.test.kaspresso.compose) androidTestImplementation(deps.test.compose.junit) androidTestImplementation(deps.test.hamcrest) + androidTestImplementation(deps.test.hilt) + kaptAndroidTest(deps.test.hilt.compiler) /** Chucker */ debugImplementation(deps.chucker) diff --git a/app/src/androidTest/kotlin/com/tangem/common/ApplicationInjectionExecutionRule.kt b/app/src/androidTest/kotlin/com/tangem/common/ApplicationInjectionExecutionRule.kt new file mode 100644 index 0000000000..e1c23e8e9b --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/ApplicationInjectionExecutionRule.kt @@ -0,0 +1,28 @@ +package com.tangem.common + +import androidx.test.core.app.ApplicationProvider +import com.tangem.tap.ApplicationEntryPoint +import com.tangem.tap.TangemApplication +import dagger.hilt.android.testing.OnComponentReadyRunner +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +class ApplicationInjectionExecutionRule : TestRule { + + private val tangemApplication: TangemApplication + get() = ApplicationProvider.getApplicationContext() + + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + override fun evaluate() { + OnComponentReadyRunner.addListener( + tangemApplication, ApplicationEntryPoint::class.java + ) { _: ApplicationEntryPoint -> + tangemApplication.init() + } + base.evaluate() + } + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt new file mode 100644 index 0000000000..4b3278bc9f --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt @@ -0,0 +1,56 @@ +package com.tangem.common + +import android.Manifest +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.espresso.intent.Intents +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.rule.GrantPermissionRule +import com.kaspersky.components.composesupport.config.withComposeSupport +import com.kaspersky.kaspresso.kaspresso.Kaspresso +import com.kaspersky.kaspresso.testcases.api.testcase.TestCase +import com.tangem.tap.MainActivity +import com.tangem.tap.domain.sdk.TangemSdkManager +import dagger.hilt.android.testing.HiltAndroidRule +import org.junit.Rule +import org.junit.rules.RuleChain +import org.junit.runner.RunWith +import javax.inject.Inject + +@RunWith(AndroidJUnit4::class) +abstract class BaseTestCase : TestCase( + kaspressoBuilder = Kaspresso.Builder.withComposeSupport() +) { + + @Inject + lateinit var tangemSdkManager: TangemSdkManager + + @get:Rule + open val composeTestRule = createAndroidComposeRule() + + @get:Rule + val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant( + Manifest.permission.POST_NOTIFICATIONS, + Manifest.permission.CAMERA + ) + + private val hiltRule = HiltAndroidRule(this) + + @Rule + @JvmField + val ruleChain = RuleChain + .outerRule(hiltRule) + .around(ApplicationInjectionExecutionRule()) + + protected fun setupHooks( + additionalBeforeSection: () -> Unit = {}, + additionalAfterSection: () -> Unit = {}, + ) = before { + hiltRule.inject() + Intents.init() + additionalBeforeSection() + }.after { + additionalAfterSection() + Intents.release() + } + +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/common/HiltTestRunner.kt b/app/src/androidTest/kotlin/com/tangem/common/HiltTestRunner.kt new file mode 100644 index 0000000000..3f5c02d5a5 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/HiltTestRunner.kt @@ -0,0 +1,17 @@ +package com.tangem.common + +import android.app.Application +import android.content.Context +import androidx.test.runner.AndroidJUnitRunner +import com.tangem.common.di.TangemMockedApplication_Application + +class HiltTestRunner : AndroidJUnitRunner() { + + override fun newApplication( + cl: ClassLoader?, + className: String?, + context: Context? + ): Application { + return super.newApplication(cl, TangemMockedApplication_Application::class.java.name, context) + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/common/TangemEmptyApplication.kt b/app/src/androidTest/kotlin/com/tangem/common/TangemEmptyApplication.kt new file mode 100644 index 0000000000..2c1cb02a42 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/TangemEmptyApplication.kt @@ -0,0 +1,11 @@ +package com.tangem.common + +import com.tangem.tap.TangemApplication + +open class TangemEmptyApplication : TangemApplication() { + + override fun onCreate() { + // super.onCreate() is not called intentionally + } + +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/common/di/TangemMockedApplication.kt b/app/src/androidTest/kotlin/com/tangem/common/di/TangemMockedApplication.kt new file mode 100644 index 0000000000..06698555e9 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/di/TangemMockedApplication.kt @@ -0,0 +1,7 @@ +package com.tangem.common.di + +import com.tangem.common.TangemEmptyApplication +import dagger.hilt.android.testing.CustomTestApplication + +@CustomTestApplication(TangemEmptyApplication::class) +internal class TangemMockedApplication \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/common/di/TestModule.kt b/app/src/androidTest/kotlin/com/tangem/common/di/TestModule.kt new file mode 100644 index 0000000000..106c04012d --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/di/TestModule.kt @@ -0,0 +1,28 @@ +package com.tangem.common.di + +import android.content.Context +import com.tangem.tap.di.TangemSdkManagerModule +import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager +import com.tangem.tap.domain.sdk.TangemSdkManager +import dagger.Module +import dagger.Provides +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import dagger.hilt.testing.TestInstallIn +import javax.inject.Singleton + +@Module +@TestInstallIn( + components = [SingletonComponent::class], + replaces = [TangemSdkManagerModule::class] +) +object TestModule { + + @Provides + @Singleton + fun provideTangemSdkManager( + @ApplicationContext context: Context + ): TangemSdkManager { + return MockTangemSdkManager(resources = context.resources) + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/helpers/base/BaseAutoTestCase.kt b/app/src/androidTest/kotlin/com/tangem/helpers/base/BaseAutoTestCase.kt deleted file mode 100644 index 6e0bfc19b2..0000000000 --- a/app/src/androidTest/kotlin/com/tangem/helpers/base/BaseAutoTestCase.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.helpers.base - -import android.Manifest -import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.rule.GrantPermissionRule -import com.kaspersky.components.composesupport.config.withComposeSupport -import com.kaspersky.kaspresso.kaspresso.Kaspresso -import com.kaspersky.kaspresso.testcases.api.testcase.TestCase -import com.tangem.tap.MainActivity -import org.junit.Rule -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -open class BaseAutoTestCase : TestCase( - kaspressoBuilder = Kaspresso.Builder.withComposeSupport() -) { - - @get:Rule - open val composeTestRule = createAndroidComposeRule() - - @get: Rule - val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.POST_NOTIFICATIONS, - Manifest.permission.CAMERA - ) -} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt index ce58b4a1be..d6418c9364 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt @@ -1,45 +1,43 @@ package com.tangem.tests import android.content.Intent.ACTION_VIEW -import androidx.test.espresso.intent.Intents -import com.tangem.helpers.base.BaseAutoTestCase +import com.tangem.common.BaseTestCase import com.tangem.screens.StoriesScreen import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL +import dagger.hilt.android.testing.HiltAndroidTest import io.github.kakaocup.compose.node.element.ComposeScreen import io.github.kakaocup.kakao.intent.KIntent import org.junit.Test -class StoriesTest : BaseAutoTestCase() { +@HiltAndroidTest +class StoriesTest : BaseTestCase() { @Test - fun clickOnButtons() = before { - Intents.init() - }.after { - Intents.release() - }.run { - ComposeScreen.onComposeScreen(composeTestRule) { - step("Click on \"Scan\" button") { - scanButton { - assertIsDisplayed() - performClick() + fun clickOnButtons() = + setupHooks().run { + ComposeScreen.onComposeScreen(composeTestRule) { + step("Click on \"Scan\" button") { + scanButton { + assertIsDisplayed() + performClick() + } } - } - step("Assert: \"Scan card\" popup opened") { - enableNFCAlert.isDisplayed() - cancelButton.click() - device.uiDevice.pressBack() - } - step("Click on \"Order\" button") { - orderButton.performClick() - } - step("Assert: browser opened") { - val expectedIntent = KIntent { - hasAction(ACTION_VIEW) - hasData(NEW_BUY_WALLET_URL) + step("Assert: \"Scan card\" popup opened") { + enableNFCAlert.isDisplayed() + cancelButton.click() + device.uiDevice.pressBack() + } + step("Click on \"Order\" button") { + orderButton.performClick() + } + step("Assert: browser opened") { + val expectedIntent = KIntent { + hasAction(ACTION_VIEW) + hasData(NEW_BUY_WALLET_URL) + } + expectedIntent.intended() + device.uiDevice.pressBack() } - expectedIntent.intended() - device.uiDevice.pressBack() } } - } } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 093b3eb247..d547bbdf53 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,7 +36,7 @@ { - val tapApplication = application as TapApplication + val tangemApplication = application as TangemApplication - return tapApplication.getAppThemeModeUseCase() + return tangemApplication.getAppThemeModeUseCase() .map { maybeMode -> maybeMode.getOrElse { AppThemeMode.DEFAULT } } diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt similarity index 75% rename from app/src/main/java/com/tangem/tap/TapApplication.kt rename to app/src/main/java/com/tangem/tap/TangemApplication.kt index 933d29882a..cf24bba208 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -64,11 +64,10 @@ import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import com.tangem.wallet.BuildConfig -import dagger.hilt.android.HiltAndroidApp -import kotlinx.coroutines.runBlocking +import dagger.hilt.EntryPoints +import kotlinx.coroutines.* import org.rekotlin.Store import timber.log.Timber -import javax.inject.Inject import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository as WalletConnect2Repository lateinit var store: Store @@ -77,101 +76,105 @@ lateinit var foregroundActivityObserver: ForegroundActivityObserver lateinit var activityResultCaller: ActivityResultCaller internal lateinit var derivationsFinder: DerivationsFinder -@HiltAndroidApp -internal class TapApplication : Application(), ImageLoaderFactory { +abstract class TangemApplication : Application(), ImageLoaderFactory { - // region Injected - @Inject - lateinit var appStateHolder: AppStateHolder + private val entryPoint: ApplicationEntryPoint + get() = EntryPoints.get(this, ApplicationEntryPoint::class.java) - @Inject - lateinit var configManager: ConfigManager + private val appStateHolder: AppStateHolder + get() = entryPoint.getAppStateHolder() - @Inject - lateinit var assetReader: AssetReader + private val configManager: ConfigManager + get() = entryPoint.getConfigManager() - @Inject - lateinit var featureTogglesManager: FeatureTogglesManager + private val assetReader: AssetReader + get() = entryPoint.getAssetReader() - @Inject - lateinit var networkConnectionManager: NetworkConnectionManager + private val featureTogglesManager: FeatureTogglesManager + get() = entryPoint.getFeatureTogglesManager() - @Inject - lateinit var customTokenFeatureToggles: CustomTokenFeatureToggles + private val networkConnectionManager: NetworkConnectionManager + get() = entryPoint.getNetworkConnectionManager() - @Inject - lateinit var walletConnect2Repository: WalletConnect2Repository + private val customTokenFeatureToggles: CustomTokenFeatureToggles + get() = entryPoint.getCustomTokenFeatureToggles() - @Inject - lateinit var walletConnectSessionsRepository: WalletConnectSessionsRepository + private val walletConnect2Repository: WalletConnect2Repository + get() = entryPoint.getWalletConnect2Repository() - @Inject - lateinit var manageTokensFeatureToggles: ManageTokensFeatureToggles + private val walletConnectSessionsRepository: WalletConnectSessionsRepository + get() = entryPoint.getWalletConnectSessionsRepository() - @Inject - lateinit var scanCardProcessor: ScanCardProcessor + private val manageTokensFeatureToggles: ManageTokensFeatureToggles + get() = entryPoint.getManageTokensFeatureToggles() - @Inject - lateinit var appCurrencyRepository: AppCurrencyRepository + private val scanCardProcessor: ScanCardProcessor + get() = entryPoint.getScanCardProcessor() - @Inject - lateinit var walletManagersFacade: WalletManagersFacade + private val appCurrencyRepository: AppCurrencyRepository + get() = entryPoint.getAppCurrencyRepository() - @Inject - lateinit var networksRepository: NetworksRepository + private val walletManagersFacade: WalletManagersFacade + get() = entryPoint.getWalletManagersFacade() - @Inject - lateinit var currenciesRepository: CurrenciesRepository + private val networksRepository: NetworksRepository + get() = entryPoint.getNetworksRepository() - @Inject - lateinit var appThemeModeRepository: AppThemeModeRepository + private val currenciesRepository: CurrenciesRepository + get() = entryPoint.getCurrenciesRepository() - @Inject - lateinit var balanceHidingRepository: BalanceHidingRepository + private val appThemeModeRepository: AppThemeModeRepository + get() = entryPoint.getAppThemeModeRepository() - @Inject - lateinit var userTokensStore: UserTokensStore + private val balanceHidingRepository: BalanceHidingRepository + get() = entryPoint.getBalanceHidingRepository() - @Inject - lateinit var getAppThemeModeUseCase: GetAppThemeModeUseCase + private val userTokensStore: UserTokensStore + get() = entryPoint.getUserTokensStore() - @Inject - lateinit var walletsRepository: WalletsRepository + val getAppThemeModeUseCase: GetAppThemeModeUseCase + get() = entryPoint.getGetAppThemeModeUseCase() - @Inject - lateinit var sendFeatureToggles: SendFeatureToggles + private val walletsRepository: WalletsRepository + get() = entryPoint.getWalletsRepository() - @Inject - lateinit var oneTimeEventFilter: OneTimeEventFilter + private val sendFeatureToggles: SendFeatureToggles + get() = entryPoint.getSendFeatureToggles() - @Inject - lateinit var generalUserWalletsListManager: UserWalletsListManager + private val oneTimeEventFilter: OneTimeEventFilter + get() = entryPoint.getOneTimeEventFilter() - @Inject - lateinit var wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase + private val generalUserWalletsListManager: UserWalletsListManager + get() = entryPoint.getGeneralUserWalletsListManager() - @Inject - lateinit var saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase + private val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase + get() = entryPoint.getWasTwinsOnboardingShownUseCase() - @Inject - lateinit var cardRepository: CardRepository + private val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase + get() = entryPoint.getSaveTwinsOnboardingShownUseCase() - @Inject - lateinit var feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles + private val cardRepository: CardRepository + get() = entryPoint.getCardRepository() - @Inject - lateinit var tangemSdkLogger: TangemSdkLogger + private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles + get() = entryPoint.getFeedbackManagerFeatureToggles() - @Inject - lateinit var settingsRepository: SettingsRepository + private val tangemSdkLogger: TangemSdkLogger + get() = entryPoint.getTangemSdkLogger() - @Inject - lateinit var blockchainSDKFactory: BlockchainSDKFactory - // endregion Injected + private val settingsRepository: SettingsRepository + get() = entryPoint.getSettingsRepository() + + private val blockchainSDKFactory: BlockchainSDKFactory + get() = entryPoint.getBlockchainSDKFactory() override fun onCreate() { super.onCreate() + init() + } + + fun init() { store = createReduxStore() if (BuildConfig.LOG_ENABLED) { diff --git a/app/src/main/java/com/tangem/tap/TangemHiltApplication.kt b/app/src/main/java/com/tangem/tap/TangemHiltApplication.kt new file mode 100644 index 0000000000..a818249392 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/TangemHiltApplication.kt @@ -0,0 +1,6 @@ +package com.tangem.tap + +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class TangemHiltApplication : TangemApplication() \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index c9b778f7c3..eb110a4d60 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -1,20 +1,15 @@ package com.tangem.tap.di -import android.content.Context -import com.tangem.domain.card.BuildConfig import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.exchange.RampStateManager -import com.tangem.tap.domain.DefaultTangemSdkManager -import com.tangem.tap.domain.MockTangemSdkManager -import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository import com.tangem.tap.network.exchangeServices.DefaultRampManager import com.tangem.tap.proxy.AppStateHolder import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -25,19 +20,6 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) internal object ActivityModule { - @Provides - @Singleton - fun provideTangemSdkManager( - @ApplicationContext context: Context, - cardSdkConfigRepository: CardSdkConfigRepository, - ): TangemSdkManager { - return if (BuildConfig.MOCK_DATA_SOURCE) { - MockTangemSdkManager(resources = context.resources) - } else { - DefaultTangemSdkManager(cardSdkConfigRepository = cardSdkConfigRepository, resources = context.resources) - } - } - @Provides @Singleton fun provideScanCardUseCase( diff --git a/app/src/main/java/com/tangem/tap/di/TangemSdkManagerModule.kt b/app/src/main/java/com/tangem/tap/di/TangemSdkManagerModule.kt new file mode 100644 index 0000000000..c6118d1ac1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/TangemSdkManagerModule.kt @@ -0,0 +1,32 @@ +package com.tangem.tap.di + +import android.content.Context +import com.tangem.domain.card.BuildConfig +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager +import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager +import com.tangem.tap.domain.sdk.TangemSdkManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class TangemSdkManagerModule { + + @Provides + @Singleton + fun provideTangemSdkManager( + @ApplicationContext context: Context, + cardSdkConfigRepository: CardSdkConfigRepository, + ): TangemSdkManager { + return if (BuildConfig.MOCK_DATA_SOURCE) { + MockTangemSdkManager(resources = context.resources) + } else { + DefaultTangemSdkManager(cardSdkConfigRepository = cardSdkConfigRepository, resources = context.resources) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt b/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt index 0f7457c20f..b87e417fab 100644 --- a/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt +++ b/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt @@ -2,7 +2,7 @@ package com.tangem.tap.di.data import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.repository.DerivationsRepository -import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.card.DefaultDerivationsRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index b6f96dfe1a..da46031149 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -9,7 +9,7 @@ import com.tangem.domain.settings.* import com.tangem.domain.settings.repositories.AppRatingRepository import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.repositories.SwapPromoRepository -import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository import dagger.Module import dagger.Provides diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt index 84d55370ed..d5fe11422f 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt @@ -13,7 +13,7 @@ import com.tangem.domain.userwallets.UserWalletIdBuilder import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.ExtendedPublicKeysMap -import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.runCatching import timber.log.Timber diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt index 8aeacbb2c9..ee417278e9 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt @@ -3,7 +3,7 @@ package com.tangem.tap.domain.scanCard.repository import com.tangem.common.CompletionResult 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.sdk.TangemSdkManager import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter // TODO: Move to the :data:card module diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt similarity index 98% rename from app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt rename to app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt index 6df4d03090..407643fb6d 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.domain +package com.tangem.tap.domain.sdk import androidx.annotation.DrawableRes import androidx.annotation.StringRes diff --git a/app/src/main/java/com/tangem/tap/domain/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt similarity index 99% rename from app/src/main/java/com/tangem/tap/domain/DefaultTangemSdkManager.kt rename to app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index 979f79f1ca..b6d9589111 100644 --- a/app/src/main/java/com/tangem/tap/domain/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.domain +package com.tangem.tap.domain.sdk.impl import android.content.res.Resources import androidx.annotation.DrawableRes @@ -29,6 +29,7 @@ import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.operations.derivation.DeriveWalletPublicKeyTask import com.tangem.tap.derivationsFinder +import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.tasks.product.CreateProductWalletTask import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask diff --git a/app/src/main/java/com/tangem/tap/domain/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt similarity index 97% rename from app/src/main/java/com/tangem/tap/domain/MockTangemSdkManager.kt rename to app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt index 0981871a0f..649707aee5 100644 --- a/app/src/main/java/com/tangem/tap/domain/MockTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.domain +package com.tangem.tap.domain.sdk.impl import android.content.res.Resources import androidx.annotation.DrawableRes @@ -14,6 +14,7 @@ import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt b/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt index 681d4ffabd..00f7f53887 100644 --- a/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt @@ -1,7 +1,7 @@ package com.tangem.tap.domain.settings import com.tangem.domain.settings.repositories.LegacySettingsRepository -import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.sdk.TangemSdkManager internal class DefaultLegacySettingsRepository( private val tangemSdkManager: TangemSdkManager, diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index 52beeb8387..d3965fa2cf 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -13,7 +13,7 @@ import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState -import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.network.exchangeServices.ExchangeService import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow diff --git a/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt index a7b0038bf6..fdf0ed8db2 100644 --- a/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt @@ -10,7 +10,7 @@ import com.tangem.domain.common.configs.MultiWalletCardConfig import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse -import com.tangem.tap.domain.DefaultTangemSdkManager +import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import io.mockk.* import kotlinx.coroutines.test.runTest diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 5e7031d48b..e0891747f6 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -197,6 +197,8 @@ test-kaspresso = { module = "com.kaspersky.android-components:kaspresso", versio test-kaspresso-compose = { module = "com.kaspersky.android-components:kaspresso-compose-support", version.ref = "kaspresso-compose"} test-compose-junit = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose-junit" } test-hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" } +test-hilt = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" } +test-hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } # endregion Test # region Other diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt index 6975388bc0..885f32c1d7 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt @@ -37,7 +37,7 @@ private fun AppExtension.configureDefaultConfig(project: Project) { buildFeatures.buildConfig = true - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunner = "com.tangem.common.HiltTestRunner" } } From 4021127ae914ec4e9482f26161a0d05ac4d21554 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Apr 2024 11:35:09 +0200 Subject: [PATCH 16/98] Updated on 2026-08-14 --- .../tokens/GetCryptoCurrencyActionsUseCase.kt | 199 +++++++++++++----- .../model/ScenarioUnavailabilityReason.kt | 26 +++ .../domain/tokens/model/TokenActionsState.kt | 16 +- .../tokendetails/TokenDetailsPreviewData.kt | 6 +- .../components/TokenDetailsActionButton.kt | 25 ++- .../components/TokenDetailsDialogConfig.kt | 19 +- .../TokenDetailsActionButtonsConverter.kt | 25 ++- .../TokenDetailsSkeletonStateConverter.kt | 8 +- .../state/factory/TokenDetailsStateFactory.kt | 80 ++++++- .../ui/components/TokenDetailsDialogs.kt | 2 +- .../viewmodels/TokenDetailsClickIntents.kt | 13 +- .../viewmodels/TokenDetailsViewModel.kt | 31 ++- .../wallet/state/model/WalletAlertState.kt | 2 +- .../wallet/state/model/WalletManageButton.kt | 60 ++++-- .../InitializeWalletsTransformer.kt | 8 +- .../SetCryptoCurrencyActionsTransformer.kt | 38 +++- .../MultiWalletCurrencyActionsConverter.kt | 11 +- .../state/utils/WalletLoadingStateFactory.kt | 12 +- .../WalletCurrencyActionsClickIntents.kt | 122 +++++++++-- 19 files changed, 543 insertions(+), 160 deletions(-) create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index 07cdfdabdc..abd618d4b1 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -73,9 +73,9 @@ class GetCryptoCurrencyActionsUseCase( walletId = userWallet.walletId, cryptoCurrencyStatus = cryptoCurrencyStatus, states = createListOfActions( - userWallet, - coinStatus, - cryptoCurrencyStatus, + userWallet = userWallet, + coinStatus = coinStatus, + cryptoCurrencyStatus = cryptoCurrencyStatus, ), ) } @@ -91,7 +91,7 @@ class GetCryptoCurrencyActionsUseCase( ): List { val cryptoCurrency = cryptoCurrencyStatus.currency if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.MissedDerivation) { - return listOf(TokenActionsState.ActionState.HideToken(true)) + return listOf(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None)) } if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Unreachable) { return getActionsForUnreachableCurrency(cryptoCurrencyStatus) @@ -102,52 +102,61 @@ class GetCryptoCurrencyActionsUseCase( // copy address if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { - activeList.add(TokenActionsState.ActionState.CopyAddress(true)) + activeList.add(TokenActionsState.ActionState.CopyAddress(ScenarioUnavailabilityReason.None)) } // receive if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { - activeList.add(TokenActionsState.ActionState.Receive(true)) + activeList.add(TokenActionsState.ActionState.Receive(ScenarioUnavailabilityReason.None)) } // send - if ( - isSendDisabled( - userWalletId = userWallet.walletId, - cryptoCurrencyStatus = cryptoCurrencyStatus, - coinStatus = coinStatus, - ) - ) { - disabledList.add(TokenActionsState.ActionState.Send(false)) + val sendUnavailabilityReason = getSendUnavailabilityReason( + userWalletId = userWallet.walletId, + cryptoCurrencyStatus = cryptoCurrencyStatus, + coinStatus = coinStatus, + ) + if (sendUnavailabilityReason == ScenarioUnavailabilityReason.None) { + activeList.add(TokenActionsState.ActionState.Send(sendUnavailabilityReason)) } else { - activeList.add(TokenActionsState.ActionState.Send(true)) + disabledList.add(TokenActionsState.ActionState.Send(sendUnavailabilityReason)) } // swap if (userWallet.isMultiCurrency) { if (marketCryptoCurrencyRepository.isExchangeable(userWallet.walletId, cryptoCurrency)) { - activeList.add(TokenActionsState.ActionState.Swap(true)) + activeList.add(TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.None)) } else { - disabledList.add(TokenActionsState.ActionState.Swap(false)) + disabledList.add( + TokenActionsState.ActionState.Swap( + unavailabilityReason = ScenarioUnavailabilityReason.NotExchangeable(cryptoCurrency.name), + ), + ) } } // buy if (rampManager.availableForBuy(cryptoCurrency)) { - activeList.add(TokenActionsState.ActionState.Buy(true)) + activeList.add(TokenActionsState.ActionState.Buy(ScenarioUnavailabilityReason.None)) } else { - disabledList.add(TokenActionsState.ActionState.Buy(false)) + disabledList.add( + TokenActionsState.ActionState.Buy(ScenarioUnavailabilityReason.BuyUnavailable(cryptoCurrency.symbol)), + ) } // sell if (rampManager.availableForSell(cryptoCurrency)) { - activeList.add(TokenActionsState.ActionState.Sell(true)) + activeList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.None)) } else { - disabledList.add(TokenActionsState.ActionState.Sell(false)) + disabledList.add( + TokenActionsState.ActionState.Sell( + unavailabilityReason = ScenarioUnavailabilityReason.SellUnavailable(cryptoCurrency.name), + ), + ) } // hide - activeList.add(TokenActionsState.ActionState.HideToken(true)) + activeList.add(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None)) return activeList + disabledList } @@ -155,63 +164,140 @@ class GetCryptoCurrencyActionsUseCase( private fun getActionsForUnreachableCurrency( cryptoCurrencyStatus: CryptoCurrencyStatus, ): List { - val activeList = mutableListOf() - val disabledList = mutableListOf() + val actionsList = mutableListOf() if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { - activeList.add(TokenActionsState.ActionState.CopyAddress(true)) + actionsList.add(TokenActionsState.ActionState.CopyAddress(ScenarioUnavailabilityReason.None)) } if (rampManager.availableForBuy(cryptoCurrencyStatus.currency)) { - activeList.add(TokenActionsState.ActionState.Buy(true)) + actionsList.add(TokenActionsState.ActionState.Buy(ScenarioUnavailabilityReason.None)) } else { - disabledList.add(TokenActionsState.ActionState.Buy(false)) + actionsList.add( + TokenActionsState.ActionState.Buy( + ScenarioUnavailabilityReason.BuyUnavailable( + cryptoCurrencyName = cryptoCurrencyStatus.currency.name, + ), + ), + ) } - disabledList.add(TokenActionsState.ActionState.Send(false)) - disabledList.add(TokenActionsState.ActionState.Swap(false)) - disabledList.add(TokenActionsState.ActionState.Sell(false)) + actionsList.add(TokenActionsState.ActionState.Send(ScenarioUnavailabilityReason.NoQuotes)) + actionsList.add(TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.NoQuotes)) + actionsList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.NoQuotes)) if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { - activeList.add(TokenActionsState.ActionState.Receive(true)) + actionsList.add(TokenActionsState.ActionState.Receive(ScenarioUnavailabilityReason.None)) } - activeList.add(TokenActionsState.ActionState.HideToken(true)) - return activeList + disabledList + actionsList.add(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None)) + return actionsList } - private suspend fun isSendDisabled( + private suspend fun getSendUnavailabilityReason( userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, coinStatus: CryptoCurrencyStatus?, - ): Boolean { - val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, cryptoCurrencyStatus.currency) - val notEnoughBalanceForFee = isNotEnoughBalanceForFee( - feePaidCurrency = feePaidCurrency, + ): ScenarioUnavailabilityReason { + val insufficientFundsForFee = insufficientFundsForFee( + userWalletId = userWalletId, tokenStatus = cryptoCurrencyStatus, coinStatus = coinStatus, ) - return cryptoCurrencyStatus.value.amount.isNullOrZero() || - notEnoughBalanceForFee || + + return when { + cryptoCurrencyStatus.value.amount.isNullOrZero() -> { + ScenarioUnavailabilityReason.EmptyBalance + } + insufficientFundsForFee != null -> { + ScenarioUnavailabilityReason.InsufficientFundsForFee( + currencyName = insufficientFundsForFee.currencyName, + networkName = insufficientFundsForFee.networkName, + feeCurrencyName = insufficientFundsForFee.feeCurrencyName, + feeCurrencySymbol = insufficientFundsForFee.feeCurrencySymbol, + ) + } currenciesRepository.hasPendingTransactions( cryptoCurrencyStatus = cryptoCurrencyStatus, coinStatus = coinStatus, - ) + ) -> { + ScenarioUnavailabilityReason.PendingTransaction(coinStatus?.currency?.symbol.orEmpty()) + } + else -> { + ScenarioUnavailabilityReason.None + } + } } - private fun isNotEnoughBalanceForFee( - feePaidCurrency: FeePaidCurrency, + private suspend fun insufficientFundsForFee( + userWalletId: UserWalletId, tokenStatus: CryptoCurrencyStatus, coinStatus: CryptoCurrencyStatus?, - ): Boolean { - return if (sendFeatureToggles.isRedesignedSendEnabled) { - tokenStatus.value.amount.isZero() - } else { - when (feePaidCurrency) { - FeePaidCurrency.Coin -> !tokenStatus.value.amount.isZero() && coinStatus?.value?.amount.isZero() - FeePaidCurrency.SameCurrency -> tokenStatus.value.amount.isZero() - is FeePaidCurrency.Token -> { - val feePaidTokenBalance = feePaidCurrency.balance - !tokenStatus.value.amount.isZero() && feePaidTokenBalance.isZero() + ): FeeInfo? { + val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, tokenStatus.currency) + coinStatus ?: return null + return when { + sendFeatureToggles.isRedesignedSendEnabled && !tokenStatus.value.amount.isZero() -> { + null + } + feePaidCurrency is FeePaidCurrency.Coin && + !tokenStatus.value.amount.isZero() && + coinStatus.value.amount.isZero() -> { + FeeInfo( + currencyName = tokenStatus.currency.name, + networkName = coinStatus.currency.network.name, + feeCurrencyName = coinStatus.currency.name, + feeCurrencySymbol = coinStatus.currency.symbol, + ) + } + feePaidCurrency is FeePaidCurrency.SameCurrency && !tokenStatus.value.amount.isZero() -> { + FeeInfo( + currencyName = tokenStatus.currency.name, + networkName = coinStatus.currency.network.name, + feeCurrencyName = coinStatus.currency.name, + feeCurrencySymbol = coinStatus.currency.symbol, + ) + } + feePaidCurrency is FeePaidCurrency.Token -> { + val feePaidTokenBalance = feePaidCurrency.balance + val amount = tokenStatus.value.amount ?: return null + if (!amount.isZero() && feePaidTokenBalance.isZero()) { + constructTokenBalanceNotEnoughWarning( + userWalletId = userWalletId, + tokenStatus = tokenStatus, + feePaidToken = feePaidCurrency, + ) + } else { + null } } + else -> null + } + } + + private suspend fun constructTokenBalanceNotEnoughWarning( + userWalletId: UserWalletId, + tokenStatus: CryptoCurrencyStatus, + feePaidToken: FeePaidCurrency.Token, + ): FeeInfo { + val token = currenciesRepository + .getMultiCurrencyWalletCurrenciesSync(userWalletId) + .find { + it is CryptoCurrency.Token && + it.contractAddress.equals(feePaidToken.contractAddress, ignoreCase = true) && + it.network.derivationPath == tokenStatus.currency.network.derivationPath + } + return if (token != null) { + FeeInfo( + currencyName = tokenStatus.currency.name, + networkName = token.network.name, + feeCurrencyName = feePaidToken.name, + feeCurrencySymbol = feePaidToken.symbol, + ) + } else { + FeeInfo( + currencyName = tokenStatus.currency.name, + networkName = tokenStatus.currency.network.name, + feeCurrencyName = feePaidToken.name, + feeCurrencySymbol = feePaidToken.symbol, + ) } } @@ -222,4 +308,11 @@ class GetCryptoCurrencyActionsUseCase( private fun BigDecimal?.isZero(): Boolean { return this?.signum() == 0 } + + data class FeeInfo( + val currencyName: String, + val networkName: String, + val feeCurrencyName: String, + val feeCurrencySymbol: String, + ) } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt new file mode 100644 index 0000000000..a3b36c4216 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt @@ -0,0 +1,26 @@ +package com.tangem.domain.tokens.model + +sealed class ScenarioUnavailabilityReason { + data object None : ScenarioUnavailabilityReason() + + // send-specific + data class PendingTransaction(val cryptoCurrencySymbol: String) : ScenarioUnavailabilityReason() + data object EmptyBalance : ScenarioUnavailabilityReason() + data class InsufficientFundsForFee( + val currencyName: String, + val networkName: String, + val feeCurrencyName: String, + val feeCurrencySymbol: String, + ) : ScenarioUnavailabilityReason() + + // buy-specific + data class BuyUnavailable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() + + // swap-specific + data class NotExchangeable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() + + // sell-specific + data class SellUnavailable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() + + data object NoQuotes : ScenarioUnavailabilityReason() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenActionsState.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenActionsState.kt index f52641b2c8..372f1e51d5 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenActionsState.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenActionsState.kt @@ -10,20 +10,20 @@ data class TokenActionsState( sealed class ActionState { - abstract val enabled: Boolean + abstract val unavailabilityReason: ScenarioUnavailabilityReason - data class Buy(override val enabled: Boolean) : ActionState() + data class Buy(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState() - data class CopyAddress(override val enabled: Boolean) : ActionState() + data class CopyAddress(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState() - data class Sell(override val enabled: Boolean) : ActionState() + data class Sell(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState() - data class Receive(override val enabled: Boolean) : ActionState() + data class Receive(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState() - data class Swap(override val enabled: Boolean) : ActionState() + data class Swap(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState() - data class Send(override val enabled: Boolean) : ActionState() + data class Send(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState() - data class HideToken(override val enabled: Boolean) : ActionState() + data class HideToken(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState() } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 77f27a4cac..e87a8f3c3c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -72,10 +72,10 @@ internal object TokenDetailsPreviewData { ) private val actionButtons = persistentListOf( - TokenDetailsActionButton.Buy(enabled = true, onClick = {}), - TokenDetailsActionButton.Send(enabled = true, onClick = {}), + TokenDetailsActionButton.Buy(dimContent = false, onClick = {}), + TokenDetailsActionButton.Send(dimContent = false, onClick = {}), TokenDetailsActionButton.Receive(onClick = {}), - TokenDetailsActionButton.Swap(enabled = true, onClick = {}), + TokenDetailsActionButton.Swap(dimContent = false, onClick = {}), ) val balanceLoading = TokenDetailsBalanceBlockState.Loading(actionButtons = actionButtons) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt index 63130c7889..85a9c70cf8 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt @@ -14,36 +14,35 @@ internal sealed class TokenDetailsActionButton(val config: ActionButtonConfig) { /** * Buy * - * @property enabled button click availability + * @property dimContent determines whether the button content will be dimmed * @property onClick lambda be invoked when Buy button is clicked */ - data class Buy(val enabled: Boolean, override val onClick: () -> Unit) : TokenDetailsActionButton( + data class Buy(val dimContent: Boolean, override val onClick: () -> Unit) : TokenDetailsActionButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_buy), iconResId = R.drawable.ic_plus_24, onClick = onClick, - enabled = enabled, + dimContent = dimContent, ), ) /** * Send * - * @property enabled button click availability + * @property dimContent determines whether the button content will be dimmed * @property onClick lambda be invoked when Send button is clicked */ - data class Send(val enabled: Boolean, override val onClick: () -> Unit) : TokenDetailsActionButton( + data class Send(val dimContent: Boolean, override val onClick: () -> Unit) : TokenDetailsActionButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_send), iconResId = R.drawable.ic_arrow_up_24, onClick = onClick, - enabled = enabled, + dimContent = dimContent, ), ) /** * Receive - * * @property onClick lambda be invoked when Receive button is clicked */ data class Receive(override val onClick: () -> Unit) : TokenDetailsActionButton( @@ -58,30 +57,30 @@ internal sealed class TokenDetailsActionButton(val config: ActionButtonConfig) { /** * Sell * - * @property enabled button click availability + * @property dimContent determines whether the button content will be dimmed * @property onClick lambda be invoked when Sell button is clicked */ - data class Sell(val enabled: Boolean, override val onClick: () -> Unit) : TokenDetailsActionButton( + data class Sell(val dimContent: Boolean, override val onClick: () -> Unit) : TokenDetailsActionButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_sell), iconResId = R.drawable.ic_currency_24, onClick = onClick, - enabled = enabled, + dimContent = dimContent, ), ) /** * Swap * - * @property enabled button click availability + * @property dimContent determines whether the button content will be dimmed * @property onClick lambda be invoked when Swap button is clicked */ - data class Swap(val enabled: Boolean, override val onClick: () -> Unit) : TokenDetailsActionButton( + data class Swap(val dimContent: Boolean, override val onClick: () -> Unit) : TokenDetailsActionButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.swapping_swap_action), iconResId = R.drawable.ic_exchange_vertical_24, onClick = onClick, - enabled = enabled, + dimContent = dimContent, ), ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt index 02fc3679f3..82bb4bbd12 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt @@ -19,7 +19,7 @@ internal data class TokenDetailsDialogConfig( sealed class DialogContentConfig { - abstract val title: TextReference + abstract val title: TextReference? abstract val message: TextReference abstract val confirmButtonConfig: ButtonConfig abstract val cancelButtonConfig: ButtonConfig? @@ -77,5 +77,22 @@ internal data class TokenDetailsDialogConfig( onClick = onConfirmClick, ) } + + data class DisabledButtonReasonDialogConfig( + val text: TextReference, + val onConfirmClick: () -> Unit, + ) : DialogContentConfig() { + + override val title = null + + override val message: TextReference = text + + override val cancelButtonConfig = null + + override val confirmButtonConfig: ButtonConfig = ButtonConfig( + text = TextReference.Res(R.string.common_ok), + onClick = onConfirmClick, + ) + } } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt index 956cdaf6d5..24f7f0c2a0 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory +import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton @@ -26,19 +27,33 @@ internal class TokenDetailsActionButtonsConverter( .mapNotNull { action -> when (action) { is TokenActionsState.ActionState.Buy -> { - TokenDetailsActionButton.Buy(enabled = action.enabled, onClick = clickIntents::onBuyClick) + TokenDetailsActionButton.Buy( + dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None, + onClick = { clickIntents.onBuyClick(action.unavailabilityReason) }, + ) } is TokenActionsState.ActionState.Receive -> { - TokenDetailsActionButton.Receive(onClick = clickIntents::onReceiveClick) + TokenDetailsActionButton.Receive( + onClick = { clickIntents.onReceiveClick(action.unavailabilityReason) }, + ) } is TokenActionsState.ActionState.Sell -> { - TokenDetailsActionButton.Sell(enabled = action.enabled, onClick = clickIntents::onSellClick) + TokenDetailsActionButton.Sell( + dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None, + onClick = { clickIntents.onSellClick(action.unavailabilityReason) }, + ) } is TokenActionsState.ActionState.Send -> { - TokenDetailsActionButton.Send(enabled = action.enabled, onClick = clickIntents::onSendClick) + TokenDetailsActionButton.Send( + dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None, + onClick = { clickIntents.onSendClick(action.unavailabilityReason) }, + ) } is TokenActionsState.ActionState.Swap -> { - TokenDetailsActionButton.Swap(enabled = action.enabled, onClick = clickIntents::onSwapClick) + TokenDetailsActionButton.Swap( + dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None, + onClick = { clickIntents.onSwapClick(action.unavailabilityReason) }, + ) } else -> { null diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt index dbc7ff632b..a88c945c5f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt @@ -84,11 +84,11 @@ internal class TokenDetailsSkeletonStateConverter( private fun createButtons(): ImmutableList { return persistentListOf( - TokenDetailsActionButton.Buy(enabled = false, onClick = {}), - TokenDetailsActionButton.Send(enabled = false, onClick = {}), + TokenDetailsActionButton.Buy(dimContent = false, onClick = {}), + TokenDetailsActionButton.Send(dimContent = false, onClick = {}), TokenDetailsActionButton.Receive(onClick = {}), - TokenDetailsActionButton.Sell(enabled = false, onClick = {}), - TokenDetailsActionButton.Swap(enabled = false, onClick = {}), + TokenDetailsActionButton.Sell(dimContent = false, onClick = {}), + TokenDetailsActionButton.Swap(dimContent = false, onClick = {}), ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 06b0c838c7..cd21841371 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -10,16 +10,12 @@ import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.triggeredEvent -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.tokens.error.CurrencyStatusError -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.NetworkAddress -import com.tangem.domain.tokens.model.TokenActionsState +import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryListError @@ -39,8 +35,9 @@ import com.tangem.utils.Provider import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import java.lang.IllegalArgumentException -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LargeClass") internal class TokenDetailsStateFactory( private val currentStateProvider: Provider, private val appCurrencyProvider: Provider, @@ -177,6 +174,19 @@ internal class TokenDetailsStateFactory( ) } + fun getStateWithActionButtonErrorDialog(unavailabilityReason: ScenarioUnavailabilityReason): TokenDetailsState { + return currentStateProvider().copy( + dialogConfig = TokenDetailsDialogConfig( + isShow = true, + onDismissRequest = clickIntents::onDismissDialog, + content = TokenDetailsDialogConfig.DialogContentConfig.DisabledButtonReasonDialogConfig( + text = getUnavailabilityReasonText(unavailabilityReason), + onConfirmClick = clickIntents::onDismissDialog, + ), + ), + ) + } + fun getRefreshingState(): TokenDetailsState { return refreshStateConverter.convert(true) } @@ -321,4 +331,60 @@ internal class TokenDetailsStateFactory( }.toImmutableList(), ) } + + private fun getUnavailabilityReasonText(unavailabilityReason: ScenarioUnavailabilityReason): TextReference { + return when (unavailabilityReason) { + // send + is ScenarioUnavailabilityReason.PendingTransaction -> { + resourceReference( + id = R.string.warning_send_blocked_pending_transactions_message, + formatArgs = wrappedList(unavailabilityReason.cryptoCurrencySymbol), + ) + } + ScenarioUnavailabilityReason.EmptyBalance -> { + resourceReference( + id = R.string.token_button_unavailability_reason_empty_balance, + ) + } + is ScenarioUnavailabilityReason.InsufficientFundsForFee -> { + resourceReference( + id = R.string.warning_send_blocked_funds_for_fee_message, + formatArgs = wrappedList( + unavailabilityReason.currencyName, + unavailabilityReason.networkName, + unavailabilityReason.currencyName, + unavailabilityReason.feeCurrencyName, + unavailabilityReason.feeCurrencySymbol, + ), + ) + } + is ScenarioUnavailabilityReason.BuyUnavailable -> { + resourceReference( + id = R.string.token_button_unavailability_reason_buy_unavailable, + formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName), + ) + } + is ScenarioUnavailabilityReason.NotExchangeable -> { + resourceReference( + id = R.string.token_button_unavailability_reason_not_exchangeable, + formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName), + ) + } + is ScenarioUnavailabilityReason.SellUnavailable -> { + resourceReference( + id = R.string.token_button_unavailability_reason_sell_unavailable, + formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName), + ) + } + ScenarioUnavailabilityReason.NoQuotes -> { + resourceReference( + id = R.string.token_button_unavailability_reason_no_quotes, + ) + } + + ScenarioUnavailabilityReason.None -> { + throw IllegalArgumentException("The unavailability reason must be other than None") + } + } + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsDialogs.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsDialogs.kt index 174eb35567..ed54c9459b 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsDialogs.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsDialogs.kt @@ -25,7 +25,7 @@ private fun TokenDetailsDialog(config: TokenDetailsDialogConfig) { onClick = config.content.confirmButtonConfig.onClick, ), onDismissDialog = config.onDismissRequest, - title = config.content.title.resolveReference(), + title = config.content.title?.resolveReference(), dismissButton = config.content.cancelButtonConfig?.let { cancelButtonConfig -> DialogButton( title = cancelButtonConfig.text.resolveReference(), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt index 712afdeb80..8b290061ed 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt @@ -1,6 +1,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel +import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.CryptoCurrency @Suppress("TooManyFunctions") @@ -8,13 +9,15 @@ interface TokenDetailsClickIntents { fun onBackClick() - fun onSendClick() + fun onReceiveClick(unavailabilityReason: ScenarioUnavailabilityReason) - fun onReceiveClick() + fun onSendClick(unavailabilityReason: ScenarioUnavailabilityReason) - fun onSellClick() + fun onSwapClick(unavailabilityReason: ScenarioUnavailabilityReason) - fun onSwapClick() + fun onBuyClick(unavailabilityReason: ScenarioUnavailabilityReason) + + fun onSellClick(unavailabilityReason: ScenarioUnavailabilityReason) fun onDismissDialog() @@ -24,8 +27,6 @@ interface TokenDetailsClickIntents { fun onRefreshSwipe() - fun onBuyClick() - fun onBuyCoinClick(cryptoCurrency: CryptoCurrency) fun onReloadClick() diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index f4c38aca5c..5722777eb6 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -26,6 +26,7 @@ import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo +import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkAddress @@ -388,9 +389,11 @@ internal class TokenDetailsViewModel @Inject constructor( router.popBackStack() } - override fun onBuyClick() { + override fun onBuyClick(unavailabilityReason: ScenarioUnavailabilityReason) { analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonBuy(cryptoCurrency.symbol)) + if (handleUnavailabilityReason(unavailabilityReason)) return + showErrorIfDemoModeOrElse { val status = cryptoCurrencyStatus ?: return@showErrorIfDemoModeOrElse @@ -417,9 +420,11 @@ internal class TokenDetailsViewModel @Inject constructor( updateTxHistory(refresh = true, showItemsLoading = true) } - override fun onSendClick() { + override fun onSendClick(unavailabilityReason: ScenarioUnavailabilityReason) { analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonSend(cryptoCurrency.symbol)) + if (handleUnavailabilityReason(unavailabilityReason)) return + sendCurrency(status = cryptoCurrencyStatus ?: return) } @@ -485,9 +490,11 @@ internal class TokenDetailsViewModel @Inject constructor( } } - override fun onReceiveClick() { + override fun onReceiveClick(unavailabilityReason: ScenarioUnavailabilityReason) { val networkAddress = cryptoCurrencyStatus?.value?.networkAddress ?: return + if (handleUnavailabilityReason(unavailabilityReason)) return + viewModelScope.launch(dispatchers.io) { analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonReceive(cryptoCurrency.symbol)) analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ReceiveScreenOpened) @@ -523,9 +530,11 @@ internal class TokenDetailsViewModel @Inject constructor( } } - override fun onSellClick() { + override fun onSellClick(unavailabilityReason: ScenarioUnavailabilityReason) { analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonSell(cryptoCurrency.symbol)) + if (handleUnavailabilityReason(unavailabilityReason)) return + showErrorIfDemoModeOrElse { val status = cryptoCurrencyStatus ?: return@showErrorIfDemoModeOrElse @@ -538,9 +547,11 @@ internal class TokenDetailsViewModel @Inject constructor( } } - override fun onSwapClick() { + override fun onSwapClick(unavailabilityReason: ScenarioUnavailabilityReason) { analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonExchange(cryptoCurrency.symbol)) + if (handleUnavailabilityReason(unavailabilityReason)) return + reduxStateHolder.dispatch(TradeCryptoAction.Swap(cryptoCurrency)) } @@ -697,7 +708,15 @@ internal class TokenDetailsViewModel @Inject constructor( shouldShowSwapPromoTokenUseCase.neverToShow() analyticsEventsHandler.send(TokenSwapPromoAnalyticsEvent.Exchange(cryptoCurrency.symbol)) } - onSwapClick() + onSwapClick(ScenarioUnavailabilityReason.None) + } + + private fun handleUnavailabilityReason(unavailabilityReason: ScenarioUnavailabilityReason): Boolean { + if (unavailabilityReason == ScenarioUnavailabilityReason.None) return false + + uiState = stateFactory.getStateWithActionButtonErrorDialog(unavailabilityReason) + + return true } private companion object { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt index b8aecf2d9f..34afb01364 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt @@ -28,7 +28,7 @@ internal sealed interface WalletAlertState { } data class DefaultAlert( - override val title: TextReference, + override val title: TextReference?, override val message: TextReference, override val onConfirmClick: (() -> Unit)?, ) : Basic() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt index 124d3bf6a9..8035434bcf 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt @@ -18,6 +18,9 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { /** Is click enabled */ abstract val enabled: Boolean + /** Whether to dim content */ + abstract val dimContent: Boolean + /** Lambda be invoked when manage button is clicked */ abstract val onClick: () -> Unit @@ -25,29 +28,40 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { * Buy * * @property enabled button click availability + * @property dimContent determines whether the button content will be dimmed * @property onClick lambda be invoked when Buy button is clicked */ - data class Buy(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( - config = ActionButtonConfig( - text = TextReference.Res(id = R.string.common_buy), - iconResId = R.drawable.ic_plus_24, - onClick = onClick, - enabled = enabled, - ), - ) + data class Buy( + override val enabled: Boolean, + override val dimContent: Boolean, + override val onClick: () -> Unit, + ) : + WalletManageButton( + config = ActionButtonConfig( + text = TextReference.Res(id = R.string.common_buy), + iconResId = R.drawable.ic_plus_24, + onClick = onClick, + dimContent = dimContent, + ), + ) /** * Send * * @property enabled button click availability + * @property dimContent determines whether the button content will be dimmed * @property onClick lambda be invoked when Send button is clicked */ - data class Send(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( + data class Send( + override val enabled: Boolean, + override val dimContent: Boolean, + override val onClick: () -> Unit, + ) : WalletManageButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_send), iconResId = R.drawable.ic_arrow_up_24, onClick = onClick, - enabled = enabled, + dimContent = dimContent, ), ) @@ -56,12 +70,16 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { * * @property onClick lambda be invoked when Receive button is clicked */ - data class Receive(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( + data class Receive( + override val enabled: Boolean, + override val dimContent: Boolean, + override val onClick: () -> Unit, + ) : WalletManageButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_receive), iconResId = R.drawable.ic_arrow_down_24, onClick = onClick, - enabled = enabled, + dimContent = dimContent, ), ) @@ -69,14 +87,19 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { * Sell * * @property enabled button click availability + * @property dimContent determines whether the button content will be dimmed * @property onClick lambda be invoked when Sell button is clicked */ - data class Sell(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( + data class Sell( + override val enabled: Boolean, + override val dimContent: Boolean, + override val onClick: () -> Unit, + ) : WalletManageButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_sell), iconResId = R.drawable.ic_currency_24, onClick = onClick, - enabled = enabled, + dimContent = dimContent, ), ) @@ -84,14 +107,19 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { * Swap * * @property enabled button click availability + * @property dimContent determines whether the button content will be dimmed * @property onClick lambda be invoked when Swap button is clicked */ - data class Swap(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( + data class Swap( + override val enabled: Boolean, + override val dimContent: Boolean, + override val onClick: () -> Unit, + ) : WalletManageButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.swapping_swap_action), iconResId = R.drawable.ic_exchange_vertical_24, onClick = onClick, - enabled = enabled, + dimContent = dimContent, ), ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt index 73065cb197..82097668ff 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt @@ -85,10 +85,10 @@ internal class InitializeWalletsTransformer( private fun createDisabledButtons(): PersistentList { return persistentListOf( - WalletManageButton.Buy(enabled = false, onClick = {}), - WalletManageButton.Send(enabled = false, onClick = {}), - WalletManageButton.Receive(enabled = false, onClick = {}), - WalletManageButton.Sell(enabled = false, onClick = {}), + WalletManageButton.Receive(enabled = false, dimContent = false, onClick = {}), + WalletManageButton.Send(enabled = false, dimContent = false, onClick = {}), + WalletManageButton.Buy(enabled = false, dimContent = false, onClick = {}), + WalletManageButton.Sell(enabled = false, dimContent = false, onClick = {}), ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt index e7fedeba18..ef747e1305 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton @@ -43,26 +44,47 @@ internal class SetCryptoCurrencyActionsTransformer( when (action) { is TokenActionsState.ActionState.Buy -> { WalletManageButton.Buy( - enabled = action.enabled, - onClick = { clickIntents.onBuyClick(cryptoCurrencyStatus) }, + enabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None, + dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None, + onClick = { + clickIntents.onBuyClick( + cryptoCurrencyStatus = cryptoCurrencyStatus, + unavailabilityReason = action.unavailabilityReason, + ) + }, ) } is TokenActionsState.ActionState.Receive -> { WalletManageButton.Receive( - enabled = action.enabled, - onClick = { clickIntents.onReceiveClick(cryptoCurrencyStatus) }, + enabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None, + dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None, + onClick = { + clickIntents.onReceiveClick(cryptoCurrencyStatus = cryptoCurrencyStatus) + }, ) } is TokenActionsState.ActionState.Sell -> { WalletManageButton.Sell( - enabled = action.enabled, - onClick = { clickIntents.onSellClick(cryptoCurrencyStatus) }, + enabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None, + dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None, + onClick = { + clickIntents.onSellClick( + cryptoCurrencyStatus = cryptoCurrencyStatus, + unavailabilityReason = action.unavailabilityReason, + ) + }, ) } is TokenActionsState.ActionState.Send -> { WalletManageButton.Send( - enabled = action.enabled, - onClick = { clickIntents.onSendClick(cryptoCurrencyStatus) }, + enabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None, + dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None, + onClick = { + clickIntents.onSendClick( + cryptoCurrencyStatus = cryptoCurrencyStatus, + unavailabilityReason = action.unavailabilityReason, + ) + }, ) } else -> { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt index 0e76d03e34..fa7478a428 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt @@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.wallets.models.UserWallet @@ -51,7 +52,7 @@ internal class MultiWalletCurrencyActionsConverter( is TokenActionsState.ActionState.Buy -> { title = resourceReference(R.string.common_buy) icon = R.drawable.ic_plus_24 - action = { clickIntents.onBuyClick(cryptoCurrencyStatus) } + action = { clickIntents.onBuyClick(cryptoCurrencyStatus, ScenarioUnavailabilityReason.None) } } is TokenActionsState.ActionState.Receive -> { title = resourceReference(R.string.common_receive) @@ -61,17 +62,17 @@ internal class MultiWalletCurrencyActionsConverter( is TokenActionsState.ActionState.Sell -> { title = resourceReference(R.string.common_sell) icon = R.drawable.ic_currency_24 - action = { clickIntents.onSellClick(cryptoCurrencyStatus) } + action = { clickIntents.onSellClick(cryptoCurrencyStatus, ScenarioUnavailabilityReason.None) } } is TokenActionsState.ActionState.Send -> { title = resourceReference(R.string.common_send) icon = R.drawable.ic_arrow_up_24 - action = { clickIntents.onSendClick(cryptoCurrencyStatus) } + action = { clickIntents.onSendClick(cryptoCurrencyStatus, ScenarioUnavailabilityReason.None) } } is TokenActionsState.ActionState.Swap -> { title = resourceReference(R.string.swapping_swap_action) icon = R.drawable.ic_exchange_horizontal_24 - action = { clickIntents.onSwapClick(cryptoCurrencyStatus) } + action = { clickIntents.onSwapClick(cryptoCurrencyStatus, ScenarioUnavailabilityReason.None) } } is TokenActionsState.ActionState.CopyAddress -> { title = resourceReference(R.string.common_copy_address) @@ -90,7 +91,7 @@ internal class MultiWalletCurrencyActionsConverter( iconResId = icon, onClick = action, isWarning = actionsState is TokenActionsState.ActionState.HideToken, - enabled = actionsState.enabled, + enabled = actionsState.unavailabilityReason == ScenarioUnavailabilityReason.None, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt index 866f9981a0..2dc37ce327 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt @@ -45,7 +45,7 @@ internal class WalletLoadingStateFactory(private val clickIntents: WalletClickIn walletCardState = userWallet.toLoadingWalletCardState(), warnings = persistentListOf(), bottomSheetConfig = null, - buttons = createDisabledButtons(), + buttons = createDimmedButtons(), marketPriceBlockState = MarketPriceBlockState.Loading(currencySymbol = currencySymbol), txHistoryState = TxHistoryState.Content( contentItems = MutableStateFlow( @@ -86,12 +86,12 @@ internal class WalletLoadingStateFactory(private val clickIntents: WalletClickIn ) } - private fun createDisabledButtons(): PersistentList { + private fun createDimmedButtons(): PersistentList { return persistentListOf( - WalletManageButton.Buy(enabled = false, onClick = {}), - WalletManageButton.Send(enabled = false, onClick = {}), - WalletManageButton.Receive(enabled = false, onClick = {}), - WalletManageButton.Sell(enabled = false, onClick = {}), + WalletManageButton.Receive(enabled = true, dimContent = true, onClick = {}), + WalletManageButton.Send(enabled = true, dimContent = true, onClick = {}), + WalletManageButton.Buy(enabled = true, dimContent = true, onClick = {}), + WalletManageButton.Sell(enabled = true, dimContent = true, onClick = {}), ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 761e754c7b..16fa32b0e7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -7,8 +7,7 @@ import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBot import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels -import com.tangem.core.ui.extensions.WrappedList -import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.* import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.common.util.cardTypesResolver @@ -19,6 +18,7 @@ import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkAddress +import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.walletmanager.WalletManagersFacade @@ -39,11 +39,18 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch +import java.lang.IllegalArgumentException import javax.inject.Inject interface WalletCurrencyActionsClickIntents { - fun onSendClick(cryptoCurrencyStatus: CryptoCurrencyStatus) + fun onSendClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason) + + fun onSellClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason) + + fun onBuyClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason) + + fun onSwapClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason) fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus) @@ -53,12 +60,6 @@ interface WalletCurrencyActionsClickIntents { fun onPerformHideToken(cryptoCurrencyStatus: CryptoCurrencyStatus) - fun onSellClick(cryptoCurrencyStatus: CryptoCurrencyStatus) - - fun onBuyClick(cryptoCurrencyStatus: CryptoCurrencyStatus) - - fun onSwapClick(cryptoCurrencyStatus: CryptoCurrencyStatus) - fun onExploreClick() } @@ -82,13 +83,18 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( private val reduxStateHolder: ReduxStateHolder, ) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents { - override fun onSendClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { + override fun onSendClick( + cryptoCurrencyStatus: CryptoCurrencyStatus, + unavailabilityReason: ScenarioUnavailabilityReason, + ) { val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return analyticsEventHandler.send( event = TokenScreenAnalyticsEvent.ButtonSend(cryptoCurrencyStatus.currency.symbol), ) + if (handleUnavailabilityReason(unavailabilityReason)) return + stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId)) viewModelScope.launch(dispatchers.main) { val maybeFeeCurrencyStatus = @@ -274,11 +280,16 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( } } - override fun onSellClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { + override fun onSellClick( + cryptoCurrencyStatus: CryptoCurrencyStatus, + unavailabilityReason: ScenarioUnavailabilityReason, + ) { analyticsEventHandler.send( event = TokenScreenAnalyticsEvent.ButtonSell(cryptoCurrencyStatus.currency.symbol), ) + if (handleUnavailabilityReason(unavailabilityReason)) return + showErrorIfDemoModeOrElse { viewModelScope.launch(dispatchers.main) { reduxStateHolder.dispatch( @@ -291,13 +302,18 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( } } - override fun onBuyClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { + override fun onBuyClick( + cryptoCurrencyStatus: CryptoCurrencyStatus, + unavailabilityReason: ScenarioUnavailabilityReason, + ) { val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return analyticsEventHandler.send( event = TokenScreenAnalyticsEvent.ButtonBuy(cryptoCurrencyStatus.currency.symbol), ) + if (handleUnavailabilityReason(unavailabilityReason)) return + showErrorIfDemoModeOrElse { viewModelScope.launch(dispatchers.main) { reduxStateHolder.dispatch( @@ -311,11 +327,16 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( } } - override fun onSwapClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { + override fun onSwapClick( + cryptoCurrencyStatus: CryptoCurrencyStatus, + unavailabilityReason: ScenarioUnavailabilityReason, + ) { analyticsEventHandler.send( event = TokenScreenAnalyticsEvent.ButtonExchange(cryptoCurrencyStatus.currency.symbol), ) + if (handleUnavailabilityReason(unavailabilityReason)) return + reduxStateHolder.dispatch(TradeCryptoAction.Swap(cryptoCurrencyStatus.currency)) } @@ -402,4 +423,79 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( action() } } + + private fun handleUnavailabilityReason(unavailabilityReason: ScenarioUnavailabilityReason): Boolean { + if (unavailabilityReason == ScenarioUnavailabilityReason.None) return false + + val unavailabilityReasonText = getUnavailabilityReasonText(unavailabilityReason) + + viewModelScope.launch(dispatchers.main) { + walletEventSender.send( + event = WalletEvent.ShowAlert( + state = WalletAlertState.DefaultAlert( + title = null, + message = unavailabilityReasonText, + onConfirmClick = null, + ), + ), + ) + } + + return true + } + + private fun getUnavailabilityReasonText(unavailabilityReason: ScenarioUnavailabilityReason): TextReference { + return when (unavailabilityReason) { + // send + is ScenarioUnavailabilityReason.PendingTransaction -> { + resourceReference( + id = R.string.warning_send_blocked_pending_transactions_message, + formatArgs = wrappedList(unavailabilityReason.cryptoCurrencySymbol), + ) + } + ScenarioUnavailabilityReason.EmptyBalance -> { + resourceReference( + id = R.string.token_button_unavailability_reason_empty_balance, + ) + } + is ScenarioUnavailabilityReason.InsufficientFundsForFee -> { + resourceReference( + id = R.string.warning_send_blocked_funds_for_fee_message, + formatArgs = wrappedList( + unavailabilityReason.currencyName, + unavailabilityReason.networkName, + unavailabilityReason.currencyName, + unavailabilityReason.feeCurrencyName, + unavailabilityReason.feeCurrencySymbol, + ), + ) + } + is ScenarioUnavailabilityReason.BuyUnavailable -> { + resourceReference( + id = R.string.token_button_unavailability_reason_buy_unavailable, + formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName), + ) + } + is ScenarioUnavailabilityReason.NotExchangeable -> { + resourceReference( + id = R.string.token_button_unavailability_reason_not_exchangeable, + formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName), + ) + } + is ScenarioUnavailabilityReason.SellUnavailable -> { + resourceReference( + id = R.string.token_button_unavailability_reason_sell_unavailable, + formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName), + ) + } + ScenarioUnavailabilityReason.NoQuotes -> { + resourceReference( + id = R.string.token_button_unavailability_reason_no_quotes, + ) + } + ScenarioUnavailabilityReason.None -> { + throw IllegalArgumentException("The unavailability reason must be other than None") + } + } + } } \ No newline at end of file From 6bb5782d717849e56d977673f0f7e33b6484abc1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Apr 2024 19:26:32 +0800 Subject: [PATCH 17/98] Updated on 2026-08-14 --- .../com/tangem/tap/ApplicationEntryPoint.kt | 4 +- .../java/com/tangem/tap/TangemApplication.kt | 2 +- .../di/domain/WalletManagersFacadeModule.kt | 2 +- .../tap/domain/twins/TwinCardsManager.kt | 3 +- .../products/twins/redux/TwinCardsAction.kt | 2 +- .../twins/ui/OnboardingTwinsFragment.kt | 2 +- .../datasource/asset/loader/AssetLoader.kt | 87 +++++++++++++++++++ .../asset/{ => reader}/AndroidAssetReader.kt | 2 +- .../asset/{ => reader}/AssetReader.kt | 2 +- .../datasource/config/FeaturesLocalLoader.kt | 3 +- .../com/tangem/datasource/di/AssetModule.kt | 4 +- .../di/TestnetTokensStorageModule.kt | 2 +- .../testnet/DefaultTestnetTokensStorage.kt | 3 +- .../di/FeatureTogglesManagerModule.kt | 2 +- .../storage/LocalFeatureTogglesStorage.kt | 3 +- .../storage/LocalFeatureTogglesStorageTest.kt | 4 +- .../DefaultWalletManagersFacade.kt | 2 +- .../SdkTransactionHistoryItemConverter.kt | 2 +- .../utils/SdkTransactionTypeConverter.kt | 3 +- .../di/BlockchainSDKFactoryModule.kt | 2 +- .../blockchainsdk/loader/LocalConfigLoader.kt | 3 +- 21 files changed, 115 insertions(+), 24 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/asset/loader/AssetLoader.kt rename core/datasource/src/main/java/com/tangem/datasource/asset/{ => reader}/AndroidAssetReader.kt (93%) rename core/datasource/src/main/java/com/tangem/datasource/asset/{ => reader}/AssetReader.kt (87%) diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index ed7ae51c32..b7b4d6a9a0 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -4,7 +4,7 @@ import com.tangem.TangemSdkLogger import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.core.analytics.filter.OneTimeEventFilter import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.datasource.local.token.UserTokensStore @@ -27,11 +27,11 @@ import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggle import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles -import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository as WalletConnect2Repository import com.tangem.tap.proxy.AppStateHolder import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository as WalletConnect2Repository @EntryPoint @InstallIn(SingletonComponent::class) diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index cf24bba208..8f79f0b7e9 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -18,7 +18,7 @@ import com.tangem.core.analytics.filter.OneTimeEventFilter import com.tangem.core.featuretoggle.manager.FeatureTogglesManager import com.tangem.datasource.api.common.MoshiConverter import com.tangem.datasource.api.common.createNetworkLoggingInterceptor -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.config.FeaturesLocalLoader import com.tangem.datasource.config.models.Config diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt index e603c9c7f8..a24becfb81 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt @@ -2,7 +2,7 @@ package com.tangem.tap.di.domain import com.squareup.moshi.Moshi import com.tangem.blockchainsdk.BlockchainSDKFactory -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.datasource.di.SdkMoshi import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.walletmanager.WalletManagersStore diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index 6ef5f2bbe9..e203d7c4d5 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -9,7 +9,7 @@ import com.tangem.common.KeyPair import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString import com.tangem.datasource.api.common.MoshiConverter -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.operations.wallet.CreateWalletResponse @@ -88,6 +88,7 @@ class TwinCardsManager( ) } + @Deprecated(message = "Use AssetReader instead") private fun getIssuers(reader: AssetReader): List { val file = reader.readJson(fileName = "tangem-app-config/issuers") return getAdapter().fromJson(file)!! diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt index 4aa4e8a4d4..5a12a5ea5e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt @@ -3,7 +3,7 @@ package com.tangem.tap.features.onboarding.products.twins.redux import com.tangem.Message import com.tangem.blockchain.common.WalletManager import com.tangem.common.extensions.VoidCallback -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.domain.TapError import com.tangem.tap.domain.twins.TwinCardsManager diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt index 75e54a693c..ca8d61618e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt @@ -15,7 +15,7 @@ import com.tangem.common.extensions.VoidCallback import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.ShareElement import com.tangem.core.ui.extensions.setStatusBarColor -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.userwallets.Artwork diff --git a/core/datasource/src/main/java/com/tangem/datasource/asset/loader/AssetLoader.kt b/core/datasource/src/main/java/com/tangem/datasource/asset/loader/AssetLoader.kt new file mode 100644 index 0000000000..e271679df4 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/asset/loader/AssetLoader.kt @@ -0,0 +1,87 @@ +package com.tangem.datasource.asset.loader + +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import com.squareup.moshi.adapter +import com.tangem.datasource.asset.reader.AssetReader +import com.tangem.datasource.di.NetworkMoshi +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runCatching +import timber.log.Timber +import javax.inject.Inject + +/** + * Asset file loader + * +[REDACTED_AUTHOR] + */ +class AssetLoader @Inject constructor( + val assetReader: AssetReader, + @NetworkMoshi val moshi: Moshi, + val dispatchers: CoroutineDispatcherProvider, +) { + + /** Load content [Content] of asset file [fileName] */ + @OptIn(ExperimentalStdlibApi::class) + suspend inline fun load(fileName: String): Content? { + return runCatching(dispatchers.io) { + val json = assetReader.readJson(fileName = fileName) + + moshi.adapter().fromJson(json) + } + .fold( + onSuccess = { parsedConfig -> + if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config is null")) + parsedConfig + }, + onFailure = { + Timber.e(it, "Failed to load config from assets") + null + }, + ) + } + + /** Load list [V] values of asset file [fileName] */ + suspend inline fun loadList(fileName: String): List { + return runCatching(dispatchers.io) { + val json = assetReader.readJson(fileName = fileName) + + val type = Types.newParameterizedType(List::class.java, V::class.java) + val adapter = moshi.adapter>(type) + + adapter.fromJson(json) + } + .fold( + onSuccess = { parsedConfig -> + if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config is null")) + parsedConfig.orEmpty() + }, + onFailure = { + Timber.e(it, "Failed to load config from assets") + emptyList() + }, + ) + } + + /** Load map [String] keys and [V] values of asset file [fileName] */ + suspend inline fun loadMap(fileName: String): Map { + return runCatching(dispatchers.io) { + val json = assetReader.readJson(fileName = fileName) + + val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java) + val adapter = moshi.adapter>(type) + + adapter.fromJson(json) + } + .fold( + onSuccess = { parsedConfig -> + if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config is null")) + parsedConfig.orEmpty() + }, + onFailure = { + Timber.e(it, "Failed to load config from assets") + emptyMap() + }, + ) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/asset/AndroidAssetReader.kt b/core/datasource/src/main/java/com/tangem/datasource/asset/reader/AndroidAssetReader.kt similarity index 93% rename from core/datasource/src/main/java/com/tangem/datasource/asset/AndroidAssetReader.kt rename to core/datasource/src/main/java/com/tangem/datasource/asset/reader/AndroidAssetReader.kt index 18c7d0fea8..4a8678e96d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/asset/AndroidAssetReader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/asset/reader/AndroidAssetReader.kt @@ -1,4 +1,4 @@ -package com.tangem.datasource.asset +package com.tangem.datasource.asset.reader import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext diff --git a/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt b/core/datasource/src/main/java/com/tangem/datasource/asset/reader/AssetReader.kt similarity index 87% rename from core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt rename to core/datasource/src/main/java/com/tangem/datasource/asset/reader/AssetReader.kt index a3196c64aa..46c3edea4a 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/asset/reader/AssetReader.kt @@ -1,4 +1,4 @@ -package com.tangem.datasource.asset +package com.tangem.datasource.asset.reader import java.io.InputStream diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/FeaturesLocalLoader.kt b/core/datasource/src/main/java/com/tangem/datasource/config/FeaturesLocalLoader.kt index 829b539552..5c162f28a8 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/FeaturesLocalLoader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/FeaturesLocalLoader.kt @@ -2,7 +2,7 @@ package com.tangem.datasource.config import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.datasource.config.models.ConfigModel import com.tangem.datasource.config.models.ConfigValueModel import com.tangem.datasource.config.models.FeatureModel @@ -11,6 +11,7 @@ import timber.log.Timber /** [REDACTED_AUTHOR] */ +@Deprecated(message = "Use AssetReader instead") class FeaturesLocalLoader( private val assetReader: AssetReader, private val moshi: Moshi, diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/AssetModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/AssetModule.kt index 7df5502310..b39cb4f1a3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/AssetModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/AssetModule.kt @@ -1,7 +1,7 @@ package com.tangem.datasource.di -import com.tangem.datasource.asset.AndroidAssetReader -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AndroidAssetReader +import com.tangem.datasource.asset.reader.AssetReader import dagger.Binds import dagger.Module import dagger.hilt.InstallIn diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/TestnetTokensStorageModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/TestnetTokensStorageModule.kt index 229722e50d..bbd6ac04ad 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/TestnetTokensStorageModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/TestnetTokensStorageModule.kt @@ -2,7 +2,7 @@ package com.tangem.datasource.di import com.squareup.moshi.Moshi import com.squareup.moshi.adapter -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.datasource.local.testnet.DefaultTestnetTokensStorage import com.tangem.datasource.local.testnet.TestnetTokensStorage import dagger.Module diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/testnet/DefaultTestnetTokensStorage.kt b/core/datasource/src/main/java/com/tangem/datasource/local/testnet/DefaultTestnetTokensStorage.kt index d4a3a066e1..878702cd4f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/testnet/DefaultTestnetTokensStorage.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/testnet/DefaultTestnetTokensStorage.kt @@ -1,7 +1,7 @@ package com.tangem.datasource.local.testnet import com.squareup.moshi.JsonAdapter -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.datasource.local.testnet.models.TestnetTokensConfig /** @@ -17,6 +17,7 @@ internal class DefaultTestnetTokensStorage( private val adapter: JsonAdapter, ) : TestnetTokensStorage { + @Deprecated(message = "Use AssetReader instead") override fun getConfig(): TestnetTokensConfig { return requireNotNull( value = adapter.fromJson( diff --git a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/di/FeatureTogglesManagerModule.kt b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/di/FeatureTogglesManagerModule.kt index e67ca2a738..c5bc28999b 100644 --- a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/di/FeatureTogglesManagerModule.kt +++ b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/di/FeatureTogglesManagerModule.kt @@ -10,7 +10,7 @@ import com.tangem.core.featuretoggle.manager.ProdFeatureTogglesManager import com.tangem.core.featuretoggle.storage.LocalFeatureTogglesStorage import com.tangem.core.featuretoggle.version.DefaultVersionProvider import com.tangem.core.featuretoggles.BuildConfig -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.datasource.local.preferences.AppPreferencesStore import dagger.Module import dagger.Provides diff --git a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/storage/LocalFeatureTogglesStorage.kt b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/storage/LocalFeatureTogglesStorage.kt index befc11c3b0..ad0f4f9939 100644 --- a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/storage/LocalFeatureTogglesStorage.kt +++ b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/storage/LocalFeatureTogglesStorage.kt @@ -3,7 +3,7 @@ package com.tangem.core.featuretoggle.storage import androidx.annotation.VisibleForTesting import com.squareup.moshi.JsonAdapter import com.tangem.core.featuretoggle.storage.LocalFeatureTogglesStorage.Companion.LOCAL_CONFIG_PATH -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import timber.log.Timber import kotlin.properties.Delegates @@ -24,6 +24,7 @@ internal class LocalFeatureTogglesStorage( override var featureToggles: List by Delegates.notNull() private set + @Deprecated(message = "Use AssetReader instead") override suspend fun init() { runCatching { requireNotNull(jsonAdapter.fromJson(assetReader.readJson(LOCAL_CONFIG_PATH))) } .onSuccess { featureToggles = it } diff --git a/core/featuretoggles/src/test/kotlin/com/tangem/core/featuretoggle/storage/LocalFeatureTogglesStorageTest.kt b/core/featuretoggles/src/test/kotlin/com/tangem/core/featuretoggle/storage/LocalFeatureTogglesStorageTest.kt index fae88c79f8..0f42883777 100644 --- a/core/featuretoggles/src/test/kotlin/com/tangem/core/featuretoggle/storage/LocalFeatureTogglesStorageTest.kt +++ b/core/featuretoggles/src/test/kotlin/com/tangem/core/featuretoggle/storage/LocalFeatureTogglesStorageTest.kt @@ -3,12 +3,11 @@ package com.tangem.core.featuretoggle.storage import android.annotation.SuppressLint import com.google.common.truth.Truth import com.squareup.moshi.JsonAdapter -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import io.mockk.coEvery import io.mockk.mockk import io.mockk.verifyAll import io.mockk.verifyOrder -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import java.io.IOException @@ -16,7 +15,6 @@ import java.io.IOException /** [REDACTED_AUTHOR] */ -@OptIn(ExperimentalCoroutinesApi::class) @SuppressLint("CheckResult") internal class LocalFeatureTogglesStorageTest { diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 42562a2735..cfbbd653ec 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -19,7 +19,7 @@ import com.tangem.blockchain.extensions.SimpleResult import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.crypto.bip39.Mnemonic import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.domain.common.util.hasDerivation diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt index 2a8c4248b7..ba58967107 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt @@ -2,7 +2,7 @@ package com.tangem.domain.walletmanager.utils import com.squareup.moshi.Moshi import com.tangem.blockchain.common.txhistory.TransactionHistoryItem -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.utils.converter.Converter import com.tangem.blockchain.common.txhistory.TransactionHistoryItem as SdkTransactionHistoryItem diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt index 67019252a1..29ea70a4cb 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt @@ -4,7 +4,7 @@ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.Types import com.tangem.blockchain.common.txhistory.TransactionHistoryItem -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.walletmanager.model.SmartContractMethod import com.tangem.utils.converter.Converter @@ -40,6 +40,7 @@ class SdkTransactionTypeConverter( } } + @Deprecated(message = "Use AssetReader instead") private fun readSmartContractMethods(): Map { val json = assetReader.readJson("contract_methods") return adapter.fromJson(json) ?: emptyMap() diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt index 28c13b2f8b..995369e069 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt @@ -11,7 +11,7 @@ import com.tangem.blockchainsdk.loader.ConfigLoader import com.tangem.blockchainsdk.loader.LocalConfigLoader import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.datasource.di.SdkMoshi import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.libs.blockchain_sdk.BuildConfig diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt index b516a909d3..a73d01c6ff 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt @@ -3,7 +3,7 @@ package com.tangem.blockchainsdk.loader import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.adapter -import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.asset.reader.AssetReader import com.tangem.datasource.config.models.ConfigValueModel import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.runCatching @@ -29,6 +29,7 @@ internal class LocalConfigLoader( @OptIn(ExperimentalStdlibApi::class) private val adapter: JsonAdapter = moshi.adapter() + @Deprecated(message = "Use AssetReader instead") override suspend fun load(): ConfigValueModel? { return runCatching(dispatchers.io) { val json = assetReader.readJson(fileName = configFileName) From 5a42f1de5f1bae02b0dd3b6f67fbefe4cef41895 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Apr 2024 17:06:45 +0800 Subject: [PATCH 18/98] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/MainActivity.kt | 5 ++++- app/src/main/java/com/tangem/tap/TangemApplication.kt | 4 +--- .../java/com/tangem/tap/features/main/MainViewModel.kt | 10 ++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index c9a42ea399..b76b496922 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -156,11 +156,14 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac private val onActivityResultCallbacks = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { - installSplashScreen() + val splashScreen = installSplashScreen() + installAppTheme() // We need to call it before onCreate to prevent unnecessary activity recreation super.onCreate(savedInstanceState) + splashScreen.setKeepOnScreenCondition { viewModel.isSplashScreenShown } + installActivityDependencies() observeAppThemeModeUpdates() diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 8f79f0b7e9..690919b008 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -65,7 +65,7 @@ import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import com.tangem.wallet.BuildConfig import dagger.hilt.EntryPoints -import kotlinx.coroutines.* +import kotlinx.coroutines.runBlocking import org.rekotlin.Store import timber.log.Timber import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository as WalletConnect2Repository @@ -198,8 +198,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { featureTogglesManager.init() store.dispatch(GlobalAction.UpdateUserWalletsListManager(generalUserWalletsListManager)) - - blockchainSDKFactory.init() } val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT) diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 1f09d65d4a..ae12f26669 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.main import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.core.navigation.ReduxNavController @@ -28,6 +29,7 @@ internal class MainViewModel @Inject constructor( private val fetchAppCurrenciesUseCase: FetchAppCurrenciesUseCase, private val deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase, private val incrementAppLaunchCounterUseCase: IncrementAppLaunchCounterUseCase, + private val blockchainSDKFactory: BlockchainSDKFactory, private val dispatchers: CoroutineDispatcherProvider, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel(), MainIntents { @@ -41,7 +43,15 @@ internal class MainViewModel @Inject constructor( val state: StateFlow = stateHolder.stateFlow + var isSplashScreenShown: Boolean = true + private set + init { + viewModelScope.launch(dispatchers.main) { + blockchainSDKFactory.init() + isSplashScreenShown = false + } + viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() } updateAppCurrencies() From 7011ffb0a8bf82fa3f00c361f1e7785a09488bac Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 10 Apr 2024 19:51:39 +0100 Subject: [PATCH 19/98] Updated on 2026-08-14 --- .../app/TangemWcBlockchainHelper.kt | 2 +- .../di/WalletConnectInteractorModule.kt | 11 +- .../domain/WalletConnectInteractor.kt | 119 ++++++++++++++++-- .../walletconnect/WalletConnectMiddleware.kt | 41 ------ .../walletconnect/WalletConnectActions.kt | 12 -- .../implementors/MultiWalletContentLoader.kt | 8 -- .../MultiWalletContentLoaderFactory.kt | 3 - .../WalletConnectNetworksSubscriber.kt | 80 ------------ .../wallet/viewmodels/WalletViewModel.kt | 14 --- 9 files changed, 118 insertions(+), 172 deletions(-) delete mode 100644 domain/legacy/src/main/java/com/tangem/domain/walletconnect/WalletConnectActions.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/WalletConnectNetworksSubscriber.kt diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt index 63e68ba973..c6ea6b5d8e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt @@ -7,7 +7,7 @@ import com.tangem.tap.domain.walletconnect2.domain.WcBlockchainHelper import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles internal class TangemWcBlockchainHelper( - private val featureToggles: WalletConnectFeatureToggles, + featureToggles: WalletConnectFeatureToggles, ) : WcBlockchainHelper { private val supportedNonEvmBlockchains = if (featureToggles.isSolanaTxSignEnabled) { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt index 927eab61da..4fed4bf821 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt @@ -6,6 +6,9 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.featuretoggle.manager.FeatureTogglesManager import com.tangem.datasource.di.SdkMoshi import com.tangem.datasource.files.FileReader +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper import com.tangem.tap.domain.walletconnect2.app.WalletConnectEventsHandlerImpl @@ -35,6 +38,9 @@ internal object WalletConnectInteractorModule { wcRepository: WalletConnectRepository, wcSessionsRepository: WalletConnectSessionsRepository, walletConnectFeatureToggles: WalletConnectFeatureToggles, + currenciesRepository: CurrenciesRepository, + walletManagersFacade: WalletManagersFacade, + walletsStateHolder: WalletsStateHolder, ): WalletConnectInteractor { return WalletConnectInteractor( handler = WalletConnectEventsHandlerImpl(), @@ -42,7 +48,10 @@ internal object WalletConnectInteractorModule { sessionsRepository = wcSessionsRepository, sdkHelper = WalletConnectSdkHelper(), blockchainHelper = TangemWcBlockchainHelper(walletConnectFeatureToggles), - dispatcher = AppCoroutineDispatcherProvider(), + currenciesRepository = currenciesRepository, + walletManagersFacade = walletManagersFacade, + walletsStateHolder = walletsStateHolder, + dispatchers = AppCoroutineDispatcherProvider(), ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index 3bf99ffd83..47c5eb12ef 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -1,26 +1,52 @@ package com.tangem.tap.domain.walletconnect2.domain +import com.tangem.blockchain.common.Blockchain +import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper import com.tangem.tap.domain.walletconnect2.domain.models.* import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.onEach +import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber +@Suppress("LargeClass", "LongParameterList") class WalletConnectInteractor( private val handler: WalletConnectEventsHandler, private val walletConnectRepository: WalletConnectRepository, private val sessionsRepository: WalletConnectSessionsRepository, private val sdkHelper: WalletConnectSdkHelper, - private val dispatcher: CoroutineDispatcherProvider, + private val dispatchers: CoroutineDispatcherProvider, + private val walletManagersFacade: WalletManagersFacade, + private val currenciesRepository: CurrenciesRepository, + private val walletsStateHolder: WalletsStateHolder, val blockchainHelper: WcBlockchainHelper, ) { + private val getSelectedWalletUseCase by lazy(LazyThreadSafetyMode.NONE) { + GetSelectedWalletUseCase(walletsStateHolder) + } + + private val wcScope = CoroutineScope( + Job() + dispatchers.io + FeatureCoroutineExceptionHandler.create("wcScope"), + ) + + private val listenerScope = CoroutineScope( + Job() + dispatchers.io + FeatureCoroutineExceptionHandler.create("listenScope"), + ) + private val events = walletConnectRepository.events private val sessions = walletConnectRepository.activeSessions @@ -34,19 +60,56 @@ class WalletConnectInteractor( sdkHelper = sdkHelper, ) - suspend fun startListening(userWalletId: String, cardId: String?) { + init { + getSelectedWalletUseCase().onRight { userWalletFlow -> + userWalletFlow + .conflate() + .distinctUntilChanged() + .onEach(::initWithWallet) + .flowOn(dispatchers.io) + .launchIn(wcScope) + } + } + + private suspend fun initWithWallet(userWallet: UserWallet) { + if (userWallet.isMultiCurrency) { + Timber.d("WalletConnect: initialize and setup networks for ${userWallet.walletId}") + startListeningWc(userWallet.walletId.stringValue, getCardId(userWallet)) + subscribeOnCurrenciesUpdates(userWallet) + } + } + + private fun subscribeOnCurrenciesUpdates(userWallet: UserWallet) { + currenciesRepository.getMultiCurrencyWalletCurrenciesUpdates(userWallet.walletId) + .conflate() + .distinctUntilChanged() + .onEach { currencies -> + setupUserChains(userWallet, currencies) + } + .flowOn(dispatchers.io) + .launchIn(wcScope) + } + + private suspend fun setupUserChains(userWallet: UserWallet, currencies: List) { + val accounts = getAccountsForWc( + userWallet = userWallet, + networks = currencies.map { it.network }, + ) + setUserChains(accounts) + } + + private suspend fun startListeningWc(userWalletId: String, cardId: String?) { this.userWalletId = userWalletId this.cardId = cardId - - coroutineScope { + listenerScope.coroutineContext.cancelChildren() + listenerScope.launch { launch { subscribeToEvents() } launch { subscribeToSessions() } - walletConnectRepository.updateSessions() } } - fun setUserChains(accounts: List) { + private fun setUserChains(accounts: List) { val userNamespaces: Map> = accounts .groupBy { account -> blockchainHelper.getNamespaceFromFullChainIdOrNull(account.chainId) @@ -106,7 +169,7 @@ class WalletConnectInteractor( } } } - .flowOn(dispatcher.io) + .flowOn(dispatchers.io) .collect() } @@ -117,7 +180,7 @@ class WalletConnectInteractor( val filteredSessions = filterSessionsForUserWallet(listOfSessions, relevantTopics) handler.onListOfSessionsUpdated(filteredSessions) } - .flowOn(dispatcher.io) + .flowOn(dispatchers.io) .collect() } @@ -258,6 +321,38 @@ class WalletConnectInteractor( return uri.lowercase().startsWith(WC_SCHEME) } + private fun getCardId(userWallet: UserWallet): String? { + return if (userWallet.scanResponse.card.backupStatus?.isActive != true) { + userWallet.cardId + } else { // if wallet has backup, any card from wallet can be used to sign + null + } + } + + private suspend fun getAccountsForWc(userWallet: UserWallet, networks: List): List { + val walletManagers = networks.mapNotNull { + val blockchain = Blockchain.fromId(it.id.value) + walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWallet.walletId, + blockchain = blockchain, + derivationPath = it.derivationPath.value, + ) + } + return walletManagers.mapNotNull { + val wallet = it.wallet + val chainId = blockchainHelper.networkIdToChainIdOrNull( + wallet.blockchain.toNetworkId(), + ) + chainId?.let { + Account( + chainId, + wallet.address, + wallet.publicKey.derivationPath?.rawPath, + ) + } + } + } + private suspend fun prepareRequestData(sessionRequest: WalletConnectEvents.SessionRequest): WcPreparedRequest? { return sessionRequestConverter.prepareRequest(sessionRequest, userWalletId) } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 9dbbdbba5d..68c3cfb669 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -6,8 +6,6 @@ import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.qrscanning.models.SourceType -import com.tangem.domain.walletconnect.WalletConnectActions -import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.inject @@ -49,28 +47,6 @@ class WalletConnectMiddleware { if (DemoHelper.tryHandle(state, action)) return when (action) { - is WalletConnectActions.New.Initialize -> { - val userWallet = action.userWallet - val cardId = if (userWallet.scanResponse.card.backupStatus?.isActive != true) { - userWallet.cardId - } else { // if wallet has backup, any card from wallet can be used to sign - null - } - scope.launch { - val wcInteractor = store.state.daggerGraphState.walletConnectInteractor ?: return@launch - wcInteractor.startListening( - userWalletId = userWallet.walletId.stringValue, - cardId = cardId, - ) - } - } - is WalletConnectActions.New.SetupUserChains -> { - scope.launch { - val userWallet = action.userWallet - val wcInteractor = store.state.daggerGraphState.walletConnectInteractor ?: return@launch - wcInteractor.setUserChains(getAccountsForWc(wcInteractor, userWallet)) - } - } is WalletConnectAction.HandleDeepLink -> { if (!action.wcUri.isNullOrBlank()) { store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri)) @@ -206,21 +182,4 @@ class WalletConnectMiddleware { private fun isWalletConnectUri(uri: String): Boolean { return walletConnectInteractor.isWalletConnectUri(uri) } - - private suspend fun getAccountsForWc(wcInteractor: WalletConnectInteractor, userWallet: UserWallet): List { - val walletManagerFacade = store.inject(DaggerGraphState::walletManagersFacade) - return walletManagerFacade.getStoredWalletManagers(userWallet.walletId).mapNotNull { - val wallet = it.wallet - val chainId = wcInteractor.blockchainHelper.networkIdToChainIdOrNull( - wallet.blockchain.toNetworkId(), - ) - chainId?.let { - Account( - chainId, - wallet.address, - wallet.publicKey.derivationPath?.rawPath, - ) - } - } - } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletconnect/WalletConnectActions.kt b/domain/legacy/src/main/java/com/tangem/domain/walletconnect/WalletConnectActions.kt deleted file mode 100644 index afb7d26d9f..0000000000 --- a/domain/legacy/src/main/java/com/tangem/domain/walletconnect/WalletConnectActions.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.domain.walletconnect - -import com.tangem.domain.wallets.models.UserWallet -import org.rekotlin.Action - -sealed class WalletConnectActions : Action { - sealed class New { - data class Initialize(val userWallet: UserWallet) : WalletConnectActions() - - data class SetupUserChains(val userWallet: UserWallet) : WalletConnectActions() - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index ba746cd830..6185f157c6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -1,7 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase -import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.wallets.models.UserWallet @@ -12,7 +11,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsCheck import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletTokenListSubscriber import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber -import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletConnectNetworksSubscriber import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents @@ -28,7 +26,6 @@ internal class MultiWalletContentLoader( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, - private val reduxStateHolder: ReduxStateHolder, ) : WalletContentLoader(id = userWallet.walletId) { override fun create(): List { @@ -50,11 +47,6 @@ internal class MultiWalletContentLoader( getMultiWalletWarningsFactory = getMultiWalletWarningsFactory, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, ), - WalletConnectNetworksSubscriber( - userWallet = userWallet, - getTokenListUseCase = getTokenListUseCase, - reduxStateHolder = reduxStateHolder, - ), ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index 794574309a..45ee41306b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -1,7 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase -import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.wallets.models.UserWallet @@ -24,7 +23,6 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val getTokenListUseCase: GetTokenListUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, - private val reduxStateHolder: ReduxStateHolder, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, ) { @@ -38,7 +36,6 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( getTokenListUseCase = getTokenListUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, getMultiWalletWarningsFactory = getMultiWalletWarningsFactory, - reduxStateHolder = reduxStateHolder, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, applyTokenListSortingUseCase = applyTokenListSortingUseCase, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/WalletConnectNetworksSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/WalletConnectNetworksSubscriber.kt deleted file mode 100644 index 420a93771a..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/WalletConnectNetworksSubscriber.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.subscribers - -import arrow.core.Either -import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.tokens.GetTokenListUseCase -import com.tangem.domain.tokens.error.TokenListError -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.NetworkGroup -import com.tangem.domain.tokens.model.TokenList -import com.tangem.domain.walletconnect.WalletConnectActions -import com.tangem.domain.wallets.models.UserWallet -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import timber.log.Timber - -/** - * WalletConnect networks subscriber. Update WalletConnect networks for a specified [userWallet]. - * - * @property userWallet user wallet - * @property getTokenListUseCase use case for subscribing on token list changes - * @property reduxStateHolder redux state holder - * -[REDACTED_AUTHOR] - */ -internal class WalletConnectNetworksSubscriber( - private val userWallet: UserWallet, - private val getTokenListUseCase: GetTokenListUseCase, - private val reduxStateHolder: ReduxStateHolder, -) : WalletSubscriber() { - - private val mutex = Mutex() - - override fun create(coroutineScope: CoroutineScope): Flow> { - return getTokenListUseCase(userWalletId = userWallet.walletId) - .conflate() - .distinctUntilCurrenciesChanged() - .filterLoadedTokens() - .onEach { - mutex.withLock { - Timber.d("WalletConnect: ${userWallet.walletId} networks is updated") - - reduxStateHolder.dispatch( - action = WalletConnectActions.New.SetupUserChains(userWallet = userWallet), - ) - } - } - } - - private fun MaybeTokenListFlow.distinctUntilCurrenciesChanged(): MaybeTokenListFlow { - return distinctUntilChanged { old, new -> - val oldCurrencies = old.fold(ifLeft = { null }, ifRight = { it.getCryptoCurrencies() }) - val newCurrencies = new.fold(ifLeft = { null }, ifRight = { it.getCryptoCurrencies() }) - - oldCurrencies == newCurrencies - } - } - - private fun MaybeTokenListFlow.filterLoadedTokens(): MaybeTokenListFlow { - return filter { either -> - either.fold( - ifLeft = { false }, - ifRight = { it.getCryptoCurrencies().isAllCurrenciesLoaded() }, - ) - } - } - - private fun TokenList.getCryptoCurrencies(): List { - return when (this) { - is TokenList.Ungrouped -> currencies - is TokenList.GroupedByNetwork -> groups.flatMap(NetworkGroup::currencies) - else -> emptyList() - } - } - - private fun List.isAllCurrenciesLoaded(): Boolean { - return none { it.value is CryptoCurrencyStatus.Loading } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 63db20714d..eb6f9dc8b2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -7,11 +7,9 @@ import androidx.lifecycle.viewModelScope import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.navigation.AppScreen import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase -import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.CanUseBiometryUseCase import com.tangem.domain.settings.IsWalletsScrollPreviewEnabled import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase -import com.tangem.domain.walletconnect.WalletConnectActions import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler @@ -37,7 +35,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") @@ -56,7 +53,6 @@ internal class WalletViewModel @Inject constructor( private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, analyticsEventsHandler: AnalyticsEventHandler, private val dispatchers: CoroutineDispatcherProvider, - private val reduxStateHolder: ReduxStateHolder, private val screenLifecycleProvider: ScreenLifecycleProvider, private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender, private val walletDeepLinksHandler: WalletDeepLinksHandler, @@ -141,16 +137,6 @@ internal class WalletViewModel @Inject constructor( .distinctUntilChanged() .onEach { selectedWallet -> if (selectedWallet.isMultiCurrency) { - Timber.d("WalletConnect: initialize and setup networks for ${selectedWallet.walletId}") - - reduxStateHolder.dispatch( - action = WalletConnectActions.New.Initialize(userWallet = selectedWallet), - ) - - reduxStateHolder.dispatch( - action = WalletConnectActions.New.SetupUserChains(userWallet = selectedWallet), - ) - selectedWalletAnalyticsSender.send(selectedWallet) } From 7907c61101cca9721f3683d66bb801ee6055ded5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Apr 2024 15:49:43 +0800 Subject: [PATCH 20/98] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../tap/common/analytics/events/ManageTokens.kt | 2 +- .../com/tangem/tap/common/extensions/WalletManager.kt | 2 +- .../tap/domain/tasks/product/DerivationsFinder.kt | 2 +- .../domain/walletconnect/WalletConnectSdkHelper.kt | 2 +- .../walletconnect2/app/TangemWcBlockchainHelper.kt | 4 ++-- .../impl/data/DefaultCustomTokenRepository.kt | 2 +- .../impl/domain/DefaultCustomTokenInteractor.kt | 2 +- .../viewmodels/AddCustomTokenViewModel.kt | 11 ++++++++--- .../redux/walletconnect/WalletConnectMiddleware.kt | 2 +- .../features/send/redux/middlewares/SendMiddleware.kt | 2 +- .../tokens/impl/data/TangemApiTokensPagingSource.kt | 2 +- .../impl/data/converters/CoinsResponseConverter.kt | 4 ++-- .../data/converters/TestnetTokensConfigConverter.kt | 2 +- .../presentation/viewmodels/TokensListViewModel.kt | 2 +- .../com/tangem/tap/proxy/TransactionManagerImpl.kt | 2 +- .../com/tangem/tap/proxy/UserWalletManagerImpl.kt | 6 +++--- data/feedback/build.gradle.kts | 1 + data/tokens/build.gradle.kts | 1 + .../data/tokens/paging/CoinsResponseConverter.kt | 2 +- .../tokens/repository/DefaultCurrenciesRepository.kt | 4 ++-- .../DefaultNetworksCompatibilityRepository.kt | 3 ++- .../tokens/repository/DefaultNetworksRepository.kt | 2 +- .../tokens/repository/DefaultTokensListRepository.kt | 2 +- .../tangem/data/tokens/utils/CryptoCurrencyFactory.kt | 4 ++-- .../com/tangem/data/tokens/utils/NetworkOperations.kt | 2 +- .../tokens/utils/ResponseCryptoCurrenciesFactory.kt | 4 ++-- .../com/tangem/data/tokens/utils/TokensOperations.kt | 2 +- .../tokens/utils/UserTokensBackwardCompatibility.kt | 4 ++-- .../data/tokens/utils/UserTokensResponseFactory.kt | 2 +- data/transaction/build.gradle.kts | 1 + data/txhistory/build.gradle.kts | 1 + data/visa/build.gradle.kts | 1 + domain/card/build.gradle.kts | 1 + .../com/tangem/domain/common/extensions/CardSdk.kt | 3 ++- .../utils/UpdateWalletManagerResultFactory.kt | 2 +- .../com/tangem/domain/features/BlockchainTests.kt | 6 +++--- domain/tokens/build.gradle.kts | 1 + domain/transaction/build.gradle.kts | 1 + domain/wallets/build.gradle.kts | 1 + features/manage-tokens/impl/build.gradle.kts | 1 + features/referral/data/build.gradle.kts | 1 + .../feature/referral/data/ReferralRepositoryImpl.kt | 2 +- features/send/impl/build.gradle.kts | 1 + .../state/confirm/SendNotificationFactory.kt | 6 +----- features/swap/data/build.gradle.kts | 1 + .../com/tangem/feature/swap/DefaultSwapRepository.kt | 2 +- .../swap/converters/LeastTokenInfoConverter.kt | 2 +- features/swap/domain/build.gradle.kts | 1 + features/tokendetails/impl/build.gradle.kts | 1 + .../factory/TokenDetailsNotificationConverter.kt | 2 +- features/wallet/impl/build.gradle.kts | 1 + .../analytics/utils/TokenListAnalyticsSender.kt | 2 +- .../com/tangem/blockchainsdk/utils}/Blockchain.kt | 2 +- 54 files changed, 73 insertions(+), 53 deletions(-) rename {domain/legacy/src/main/java/com/tangem/domain/common/extensions => libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils}/Blockchain.kt (99%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 998c7c7546..a4c7cd0cfd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -43,6 +43,7 @@ configurations.all { dependencies { implementation(projects.domain.legacy) + implementation(projects.libs.blockchainSdk) implementation(projects.domain.models) implementation(projects.domain.core) implementation(projects.domain.card) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt index 969d1d47fb..9bf97f2250 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt @@ -1,7 +1,7 @@ package com.tangem.tap.common.analytics.events +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.core.analytics.models.AnalyticsEvent -import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.tap.common.extensions.filterNotNull diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index f4df731c74..5347ee2efc 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -4,8 +4,8 @@ import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.AddressType +import com.tangem.blockchainsdk.utils.amountToCreateAccount import com.tangem.common.services.Result -import com.tangem.domain.common.extensions.amountToCreateAccount import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.tap.common.TestActions import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt index d229d8f05d..cb4b7918e9 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt @@ -3,11 +3,11 @@ package com.tangem.tap.domain.tasks.product import com.tangem.blockchain.blockchains.cardano.CardanoUtils import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.derivation.DerivationStyle +import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.datasource.local.token.UserTokensStore import com.tangem.domain.common.DerivationStyleProvider import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation -import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.userwallets.UserWalletIdBuilder import com.tangem.domain.wallets.models.UserWalletId diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index 015a72b111..16bc4b280d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -8,6 +8,7 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.toKeccak import com.tangem.blockchain.common.* import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.extensions.* +import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.common.CompletionResult import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toDecompressedPublicKey @@ -15,7 +16,6 @@ import com.tangem.common.extensions.toHexString import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType -import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.operations.sign.SignHashCommand import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.safeUpdate diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt index c6ea6b5d8e..edddd1a886 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt @@ -1,8 +1,8 @@ package com.tangem.tap.domain.walletconnect2.app import com.tangem.blockchain.common.Blockchain -import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.tap.domain.walletconnect2.domain.WcBlockchainHelper import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/DefaultCustomTokenRepository.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/DefaultCustomTokenRepository.kt index 96f2eea824..e9b45a2623 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/DefaultCustomTokenRepository.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/DefaultCustomTokenRepository.kt @@ -1,10 +1,10 @@ package com.tangem.tap.features.customtoken.impl.data import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.domain.common.extensions.supportedBlockchains -import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.common.util.cardTypesResolver import com.tangem.tap.features.customtoken.impl.data.converters.FoundTokenConverter import com.tangem.tap.features.customtoken.impl.domain.CustomTokenRepository diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt index 2de3e037af..de73abac25 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt @@ -1,9 +1,9 @@ package com.tangem.tap.features.customtoken.impl.domain import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.data.tokens.utils.CryptoCurrencyFactory import com.tangem.domain.card.DerivePublicKeysUseCase -import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.domain.models.scan.ScanResponse diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt index 3dd2804b29..5a4cafa1bb 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt @@ -13,10 +13,15 @@ import androidx.lifecycle.viewModelScope import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.derivation.DerivationStyle +import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.blockchainsdk.utils.isSupportedInApp +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.common.DerivationStyleProvider -import com.tangem.domain.common.extensions.* +import com.tangem.domain.common.extensions.canHandleBlockchain +import com.tangem.domain.common.extensions.canHandleToken +import com.tangem.domain.common.extensions.supportedBlockchains import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.features.addCustomToken.CustomCurrency @@ -232,7 +237,7 @@ internal class AddCustomTokenViewModel @Inject constructor( ifRight = { it.scanResponse }, ) val derivationStyle = scanResponse?.derivationStyleProvider?.getDerivationStyle() - return listOf(defaultNetwork) + Blockchain.values() + return listOf(defaultNetwork) + Blockchain.entries .filter { blockchain -> scanResponse?.card?.supportedBlockchains(scanResponse.cardTypesResolver) ?.contains(blockchain) == true && isDerivationPathNotEmpty(derivationStyle, blockchain) @@ -316,7 +321,7 @@ internal class AddCustomTokenViewModel @Inject constructor( type = DerivationPathSelectorType.CUSTOM, derivationPath = "", ), - ) + Blockchain.values() + ) + Blockchain.entries .filter { blockchain -> blockchain.isSupportedInApp() && !blockchain.isTestnet() } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 68c3cfb669..f29861dbbc 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -2,9 +2,9 @@ package com.tangem.tap.features.details.redux.walletconnect import androidx.core.os.bundleOf import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction -import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.qrscanning.models.SourceType import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.tap.common.extensions.dispatchOnMain diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index dd10c08389..2922bd5b22 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -12,13 +12,13 @@ import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder.XrpTransactio import com.tangem.blockchain.common.* import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.extensions.SimpleResult +import com.tangem.blockchainsdk.utils.minimalAmount import com.tangem.common.core.TangemSdkError import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.TapWorkarounds.isStart2Coin -import com.tangem.domain.common.extensions.minimalAmount import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.tokens.legacy.TradeCryptoAction diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt index acf336dac3..bfd30da35b 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt @@ -3,10 +3,10 @@ package com.tangem.tap.features.tokens.impl.data import androidx.paging.PagingSource import androidx.paging.PagingState import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.domain.common.extensions.supportedBlockchains -import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.tap.features.tokens.impl.data.converters.CoinsResponseConverter diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/CoinsResponseConverter.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/CoinsResponseConverter.kt index 2bffd45d01..2d620d45e8 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/CoinsResponseConverter.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/CoinsResponseConverter.kt @@ -1,9 +1,9 @@ package com.tangem.tap.features.tokens.impl.data.converters import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.blockchainsdk.utils.isSupportedInApp import com.tangem.datasource.api.tangemTech.models.CoinsResponse -import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.domain.common.extensions.isSupportedInApp import com.tangem.tap.features.tokens.impl.domain.models.Token import com.tangem.utils.converter.Converter diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/TestnetTokensConfigConverter.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/TestnetTokensConfigConverter.kt index d1babd19b7..561c8c123a 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/TestnetTokensConfigConverter.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/TestnetTokensConfigConverter.kt @@ -1,8 +1,8 @@ package com.tangem.tap.features.tokens.impl.data.converters import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.datasource.local.testnet.models.TestnetTokensConfig -import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.tap.features.tokens.impl.domain.models.Token import com.tangem.utils.converter.Converter diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt index 0fa1976240..53867da367 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt @@ -10,6 +10,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.* import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction @@ -19,7 +20,6 @@ import com.tangem.domain.card.DerivePublicKeysUseCase import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.extensions.canHandleBlockchain import com.tangem.domain.common.extensions.canHandleToken -import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.supportedTokens import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index 4c7a8f4461..3782449fe2 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -21,10 +21,10 @@ import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult import com.tangem.blockchain.externallinkprovider.TxExploreState import com.tangem.blockchain.network.ResultChecker +import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.models.* diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 8d098f79c1..43c70cab02 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -4,10 +4,10 @@ import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.blockchainsdk.utils.toCoinId +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.domain.common.extensions.toCoinId -import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.walletmanager.WalletManagersFacade diff --git a/data/feedback/build.gradle.kts b/data/feedback/build.gradle.kts index 306603b7c2..4847e15ddd 100644 --- a/data/feedback/build.gradle.kts +++ b/data/feedback/build.gradle.kts @@ -37,6 +37,7 @@ dependencies { implementation(projects.domain.feedback) implementation(projects.domain.legacy) + implementation(projects.libs.blockchainSdk) implementation(projects.domain.models) implementation(projects.domain.wallets.models) } \ No newline at end of file diff --git a/data/tokens/build.gradle.kts b/data/tokens/build.gradle.kts index f183caa14f..7585dedc01 100644 --- a/data/tokens/build.gradle.kts +++ b/data/tokens/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { /** Project - Utils */ implementation(projects.core.utils) implementation(projects.domain.legacy) + implementation(projects.libs.blockchainSdk) /** Tangem SDKs */ implementation(deps.tangem.blockchain) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsResponseConverter.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsResponseConverter.kt index b6d7d5395e..17e39d7f0d 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsResponseConverter.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsResponseConverter.kt @@ -1,9 +1,9 @@ package com.tangem.data.tokens.paging import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.data.tokens.utils.getNetworkStandardType import com.tangem.datasource.api.tangemTech.models.CoinsResponse -import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Token import com.tangem.utils.converter.Converter diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 9a749d8a1d..f15c13019f 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -2,6 +2,8 @@ package com.tangem.data.tokens.repository import arrow.core.raise.catch import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.toCoinId +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.data.common.api.safeApiCall import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.tokens.utils.* @@ -16,8 +18,6 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.local.token.AssetsStore import com.tangem.datasource.local.token.UserTokensStore import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.common.extensions.toCoinId -import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.common.util.hasDerivation import com.tangem.domain.core.error.DataError diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksCompatibilityRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksCompatibilityRepository.kt index 006ca60bb9..02fdef2739 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksCompatibilityRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksCompatibilityRepository.kt @@ -1,6 +1,7 @@ package com.tangem.data.tokens.repository import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.common.card.EllipticCurve import com.tangem.data.tokens.utils.getNetwork import com.tangem.datasource.local.userwallet.UserWalletsStore @@ -63,7 +64,7 @@ internal class DefaultNetworksCompatibilityRepository( @Throws(IllegalArgumentException::class) override suspend fun getSupportedNetworks(userWalletId: UserWalletId): List { val scanResponse = getWalletOrThrow(userWalletId).scanResponse - return Blockchain.values() + return Blockchain.entries .filter { blockchain -> scanResponse.card.supportedBlockchains(scanResponse.cardTypesResolver).contains(blockchain) } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index b8e9d5c47c..b3092b4f38 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -2,6 +2,7 @@ package com.tangem.data.tokens.repository import arrow.core.raise.catch import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory import com.tangem.data.tokens.utils.NetworkStatusFactory @@ -9,7 +10,6 @@ import com.tangem.data.tokens.utils.ResponseCryptoCurrenciesFactory import com.tangem.datasource.local.network.NetworksStatusesStore import com.tangem.datasource.local.token.UserTokensStore import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.core.lce.lceFlow diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensListRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensListRepository.kt index e083c266df..a10187713a 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensListRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensListRepository.kt @@ -4,11 +4,11 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.data.tokens.paging.CoinsPagingSource import com.tangem.data.tokens.utils.FoundTokenConverter import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.tokens.model.FoundToken import com.tangem.domain.tokens.model.Token import com.tangem.domain.tokens.repository.QuotesRepository diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CryptoCurrencyFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CryptoCurrencyFactory.kt index 9d9e0f3cbc..3b4bfb61e0 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CryptoCurrencyFactory.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CryptoCurrencyFactory.kt @@ -1,9 +1,9 @@ package com.tangem.data.tokens.utils import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.blockchainsdk.utils.toCoinId import com.tangem.domain.common.DerivationStyleProvider -import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.domain.common.extensions.toCoinId import com.tangem.domain.tokens.model.CryptoCurrency import timber.log.Timber import com.tangem.blockchain.common.Token as SdkToken diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkOperations.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkOperations.kt index 8b2a7175c6..38f09a5623 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkOperations.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkOperations.kt @@ -1,8 +1,8 @@ package com.tangem.data.tokens.utils import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.domain.common.DerivationStyleProvider -import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.tokens.model.Network import timber.log.Timber diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/ResponseCryptoCurrenciesFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/ResponseCryptoCurrenciesFactory.kt index 5435cebfd9..aa21c01410 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/ResponseCryptoCurrenciesFactory.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/ResponseCryptoCurrenciesFactory.kt @@ -2,10 +2,10 @@ package com.tangem.data.tokens.utils import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token +import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.blockchainsdk.utils.toCoinId import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.common.DerivationStyleProvider -import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.domain.common.extensions.toCoinId import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.models.scan.ScanResponse diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/TokensOperations.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/TokensOperations.kt index 54f468860b..a944703601 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/TokensOperations.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/TokensOperations.kt @@ -2,8 +2,8 @@ package com.tangem.data.tokens.utils import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.IconsUtil +import com.tangem.blockchainsdk.utils.toCoinId import com.tangem.datasource.api.tangemTech.models.UserTokensResponse -import com.tangem.domain.common.extensions.toCoinId import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency.ID import com.tangem.domain.tokens.model.Network diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt index db1650880f..ec40d6e52c 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt @@ -1,9 +1,9 @@ package com.tangem.data.tokens.utils import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.blockchainsdk.utils.toCoinId import com.tangem.datasource.api.tangemTech.models.UserTokensResponse -import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.domain.common.extensions.toCoinId /** * Helper to apply compatibility changes for [UserTokensResponse] to support old saved tokens diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensResponseFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensResponseFactory.kt index 7e75db2896..7e8372da6c 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensResponseFactory.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensResponseFactory.kt @@ -1,7 +1,7 @@ package com.tangem.data.tokens.utils +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.datasource.api.tangemTech.models.UserTokensResponse -import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.tokens.model.CryptoCurrency class UserTokensResponseFactory { diff --git a/data/transaction/build.gradle.kts b/data/transaction/build.gradle.kts index e1b448fb10..6797014fd2 100644 --- a/data/transaction/build.gradle.kts +++ b/data/transaction/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { /** Domain */ implementation(projects.domain.transaction) implementation(projects.domain.legacy) + implementation(projects.libs.blockchainSdk) implementation(projects.domain.wallets.models) implementation(projects.domain.tokens.models) diff --git a/data/txhistory/build.gradle.kts b/data/txhistory/build.gradle.kts index 2b5b2f8165..3a8e769a69 100644 --- a/data/txhistory/build.gradle.kts +++ b/data/txhistory/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { implementation(projects.core.utils) implementation(projects.core.datasource) implementation(projects.domain.legacy) + implementation(projects.libs.blockchainSdk) implementation(projects.domain.tokens.models) implementation(projects.domain.txhistory) implementation(projects.domain.txhistory.models) diff --git a/data/visa/build.gradle.kts b/data/visa/build.gradle.kts index ac55319dfe..e552eb58b0 100644 --- a/data/visa/build.gradle.kts +++ b/data/visa/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { /** Project - Utils */ implementation(projects.core.utils) implementation(projects.domain.legacy) + implementation(projects.libs.blockchainSdk) /** Project - Libs */ debugImplementation(projects.libs.visa) diff --git a/domain/card/build.gradle.kts b/domain/card/build.gradle.kts index 4ec562467f..d317bafbf5 100644 --- a/domain/card/build.gradle.kts +++ b/domain/card/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { implementation(projects.domain.demo) implementation(projects.domain.core) implementation(projects.domain.legacy) + implementation(projects.libs.blockchainSdk) // TODO: Remove after new card scan result was implemented implementation(projects.domain.models) implementation(projects.domain.tokens.models) diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt index aea826fa7c..cf827af989 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt @@ -1,6 +1,7 @@ package com.tangem.domain.common.extensions import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.isSupportedInApp import com.tangem.common.card.EllipticCurve import com.tangem.common.card.FirmwareVersion import com.tangem.domain.common.CardTypesResolver @@ -22,7 +23,7 @@ fun CardDTO.supportedBlockchains(cardTypesResolver: CardTypesResolver): List Date: Fri, 12 Apr 2024 20:29:29 +0800 Subject: [PATCH 21/98] Updated on 2026-08-14 --- core/datasource/build.gradle.kts | 1 + .../datasource/config/models/ProviderModel.kt | 32 ++++++++++++ .../com/tangem/datasource/di/MoshiModule.kt | 8 +++ gradle/dependencies.toml | 1 + .../DefaultBlockchainSDKFactory.kt | 15 ++++-- .../di/BlockchainSDKFactoryModule.kt | 27 ++-------- .../blockchainsdk/loader/ConfigLoader.kt | 14 ------ .../blockchainsdk/loader/LocalConfigLoader.kt | 49 ------------------- 8 files changed, 56 insertions(+), 91 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/config/models/ProviderModel.kt delete mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/ConfigLoader.kt delete mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 6aff99a356..06a4d5180e 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { /** Network */ implementation(deps.moshi) implementation(deps.moshi.kotlin) + implementation(deps.moshi.adapters) implementation(deps.okHttp) implementation(deps.okHttp.prettyLogging) implementation(deps.retrofit) diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/ProviderModel.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/ProviderModel.kt new file mode 100644 index 0000000000..6bd7f5c07a --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/ProviderModel.kt @@ -0,0 +1,32 @@ +package com.tangem.datasource.config.models + +import com.squareup.moshi.Json + +/** Config provider model */ +sealed class ProviderModel { + + /** + * Example, + * { + * "type": "public", + * "url": "https://example.com" + * } + */ + data class Public( + @Json(name = "url") val url: String, + ) : ProviderModel() + + /** + * Example, + * { + * "type": "private", + * "name": "nownodes" + * } + */ + data class Private( + @Json(name = "name") val name: String, + ) : ProviderModel() + + /** Unsupported type */ + data object UnsupportedType : ProviderModel() +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt index c22d4433bf..3884c6c9a5 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt @@ -1,11 +1,13 @@ package com.tangem.datasource.di import com.squareup.moshi.Moshi +import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tangem.common.json.MoshiJsonConverter import com.tangem.datasource.api.common.BigDecimalAdapter import com.tangem.datasource.api.common.DateTimeAdapter import com.tangem.datasource.api.common.LocalDateAdapter +import com.tangem.datasource.config.models.ProviderModel import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -21,6 +23,12 @@ class MoshiModule { @NetworkMoshi fun provideNetworkMoshi(): Moshi { return Moshi.Builder() + .add( + PolymorphicJsonAdapterFactory.of(ProviderModel::class.java, "type") + .withSubtype(ProviderModel.Public::class.java, "public") + .withSubtype(ProviderModel.Private::class.java, "private") + .withDefaultValue(ProviderModel.UnsupportedType), + ) .add(BigDecimalAdapter()) .add(LocalDateAdapter()) .add(DateTimeAdapter()) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index e0891747f6..eab18a906d 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -223,6 +223,7 @@ lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" } material = { module = "com.google.android.material:material", version.ref = "googleMaterialComponent" } moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" } moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } +moshi-adapters = { module = "com.squareup.moshi:moshi-adapters", version.ref = "moshi" } okHttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okHttp-prettyLogging = { module = "com.github.ihsanbal:LoggingInterceptor", version.ref = "okHttp-prettyLogging" } spongecastle-core = { module = "com.madgag.spongycastle:core", version.ref = "spongycastleCryptoCore" } diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt index 7b059dfa48..891dbc65b8 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt @@ -6,7 +6,9 @@ import com.tangem.blockchain.common.datastorage.BlockchainDataStorage import com.tangem.blockchain.common.logging.BlockchainSDKLogger import com.tangem.blockchainsdk.config.ConfigStorage import com.tangem.blockchainsdk.converters.BlockchainSDKConfigConverter -import com.tangem.blockchainsdk.loader.ConfigLoader +import com.tangem.datasource.asset.loader.AssetLoader +import com.tangem.datasource.config.models.ConfigValueModel +import com.tangem.libs.blockchain_sdk.BuildConfig import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map @@ -14,7 +16,7 @@ import kotlinx.coroutines.flow.map /** * Implementation of Blockchain SDK components factory * - * @property configLoader config loader + * @property assetLoader asset loader * @property configStorage config storage * @property accountCreator account creator * @property blockchainDataStorage blockchain data storage @@ -23,7 +25,7 @@ import kotlinx.coroutines.flow.map [REDACTED_AUTHOR] */ internal class DefaultBlockchainSDKFactory( - private val configLoader: ConfigLoader, + private val assetLoader: AssetLoader, private val configStorage: ConfigStorage, private val accountCreator: AccountCreator, private val blockchainDataStorage: BlockchainDataStorage, @@ -33,7 +35,7 @@ internal class DefaultBlockchainSDKFactory( override val walletManagerFactory: Flow by lazy(::createWalletManagerFactory) override suspend fun init() { - val configValueModel = configLoader.load() ?: return + val configValueModel = assetLoader.load(CONFIG_FILE_NAME) ?: return configStorage.store( config = BlockchainSDKConfigConverter.convert(value = configValueModel), @@ -52,4 +54,9 @@ internal class DefaultBlockchainSDKFactory( ) } } + + private companion object { + + const val CONFIG_FILE_NAME = "tangem-app-config/config_${BuildConfig.ENVIRONMENT}" + } } \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt index 995369e069..4228700264 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt @@ -1,21 +1,15 @@ package com.tangem.blockchainsdk.di -import com.squareup.moshi.Moshi import com.tangem.blockchain.common.logging.BlockchainSDKLogger import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.blockchainsdk.DefaultBlockchainSDKFactory import com.tangem.blockchainsdk.accountcreator.DefaultAccountCreator import com.tangem.blockchainsdk.config.RuntimeConfigStorage import com.tangem.blockchainsdk.datastorage.DefaultBlockchainDataStorage -import com.tangem.blockchainsdk.loader.ConfigLoader -import com.tangem.blockchainsdk.loader.LocalConfigLoader import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.asset.reader.AssetReader -import com.tangem.datasource.di.SdkMoshi +import com.tangem.datasource.asset.loader.AssetLoader import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.libs.blockchain_sdk.BuildConfig -import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -29,33 +23,18 @@ internal object BlockchainSDKFactoryModule { @Provides @Singleton fun provideBlockchainSDKFactory( - @SdkMoshi moshi: Moshi, - assetReader: AssetReader, - dispatchers: AppCoroutineDispatcherProvider, + assetLoader: AssetLoader, authProvider: AuthProvider, tangemTechApi: TangemTechApi, appPreferencesStore: AppPreferencesStore, blockchainSDKLogger: BlockchainSDKLogger, ): BlockchainSDKFactory { return DefaultBlockchainSDKFactory( - configLoader = createConfigLoader(moshi, assetReader, dispatchers), + assetLoader = assetLoader, configStorage = RuntimeConfigStorage(), accountCreator = DefaultAccountCreator(authProvider, tangemTechApi), blockchainDataStorage = DefaultBlockchainDataStorage(appPreferencesStore), blockchainSDKLogger = blockchainSDKLogger, ) } - - private fun createConfigLoader( - moshi: Moshi, - assetReader: AssetReader, - dispatchers: AppCoroutineDispatcherProvider, - ): ConfigLoader { - return LocalConfigLoader( - buildEnvironment = BuildConfig.ENVIRONMENT, - moshi = moshi, - assetReader = assetReader, - dispatchers = dispatchers, - ) - } } \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/ConfigLoader.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/ConfigLoader.kt deleted file mode 100644 index 570013fd4a..0000000000 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/ConfigLoader.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.blockchainsdk.loader - -import com.tangem.datasource.config.models.ConfigValueModel - -/** - * Config loader - * -[REDACTED_AUTHOR] - */ -internal interface ConfigLoader { - - /** Load config [ConfigValueModel] */ - suspend fun load(): ConfigValueModel? -} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt deleted file mode 100644 index a73d01c6ff..0000000000 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.tangem.blockchainsdk.loader - -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.Moshi -import com.squareup.moshi.adapter -import com.tangem.datasource.asset.reader.AssetReader -import com.tangem.datasource.config.models.ConfigValueModel -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.runCatching -import timber.log.Timber - -/** - * Local config loader - * - * @param buildEnvironment build environment that defines config file name - * @param moshi moshi - * @property assetReader asset reader - * @property dispatchers dispatchers - */ -internal class LocalConfigLoader( - buildEnvironment: String, - moshi: Moshi, - private val assetReader: AssetReader, - private val dispatchers: CoroutineDispatcherProvider, -) : ConfigLoader { - - private val configFileName = "tangem-app-config/config_$buildEnvironment" - - @OptIn(ExperimentalStdlibApi::class) - private val adapter: JsonAdapter = moshi.adapter() - - @Deprecated(message = "Use AssetReader instead") - override suspend fun load(): ConfigValueModel? { - return runCatching(dispatchers.io) { - val json = assetReader.readJson(fileName = configFileName) - adapter.fromJson(json) - } - .fold( - onSuccess = { parsedConfig -> - if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config is null")) - parsedConfig - }, - onFailure = { - Timber.e(it, "Failed to load config from assets") - null - }, - ) - } -} \ No newline at end of file From 59680b53052d2fa7652d3b195bb3f5fa6ce9cd8c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Apr 2024 12:44:54 +0400 Subject: [PATCH 22/98] Updated on 2026-08-14 --- .../domain/WalletConnectInteractor.kt | 2 +- .../domain/tokens/GetTokenListUseCase.kt | 68 ++++++-- .../CurrenciesStatusesLceOperations.kt | 153 ++++++++++++++++++ .../domain/tokens/GetTokenListUseCaseTest.kt | 30 ++-- .../organizetokens/OrganizeTokensViewModel.kt | 2 +- .../domain/GetMultiWalletWarningsFactory.kt | 2 +- .../MultiWalletTokenListSubscriber.kt | 2 +- 7 files changed, 225 insertions(+), 34 deletions(-) create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index 47c5eb12ef..d7b74b884b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -1,7 +1,7 @@ package com.tangem.tap.domain.walletconnect2.domain import com.tangem.blockchain.common.Blockchain -import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrenciesRepository diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt index a19f2c9cd2..afb4450d1c 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt @@ -1,11 +1,16 @@ package com.tangem.domain.tokens -import arrow.core.Either import arrow.core.left +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.core.utils.EitherFlow +import com.tangem.domain.core.utils.lceError +import com.tangem.domain.core.utils.lceLoading +import com.tangem.domain.core.utils.toLce import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.error.mapper.mapToTokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.operations.CurrenciesStatusesLceOperations import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations import com.tangem.domain.tokens.operations.TokenListOperations import com.tangem.domain.tokens.repository.CurrenciesRepository @@ -13,7 +18,6 @@ import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.transformLatest @@ -25,11 +29,18 @@ class GetTokenListUseCase( ) { @OptIn(ExperimentalCoroutinesApi::class) - operator fun invoke(userWalletId: UserWalletId): Flow> { - return getTokensStatuses(userWalletId).transformLatest { maybeTokens -> + fun launch(userWalletId: UserWalletId): EitherFlow { + val operations = CurrenciesStatusesOperations( + userWalletId = userWalletId, + currenciesRepository = currenciesRepository, + quotesRepository = quotesRepository, + networksRepository = networksRepository, + ) + + return operations.getCurrenciesStatusesFlow().transformLatest { maybeTokens -> maybeTokens.fold( ifLeft = { error -> - emit(error.left()) + emit(error.mapToTokenListError().left()) }, ifRight = { tokens -> emitAll(createTokenList(userWalletId, tokens)) @@ -38,26 +49,35 @@ class GetTokenListUseCase( } } - private fun getTokensStatuses( - userWalletId: UserWalletId, - ): Flow>> { - val operations = CurrenciesStatusesOperations( - userWalletId = userWalletId, + @OptIn(ExperimentalCoroutinesApi::class) + fun launchLce(userWalletId: UserWalletId): LceFlow { + val operations = CurrenciesStatusesLceOperations( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, ) - return operations.getCurrenciesStatusesFlow() - .map { maybeCurrenciesStatuses -> - maybeCurrenciesStatuses.mapLeft(CurrenciesStatusesOperations.Error::mapToTokenListError) - } + return operations.getCurrenciesStatuses(userWalletId).transformLatest { maybeCurrencies -> + maybeCurrencies.fold( + ifLoading = { maybeContent -> + if (maybeContent != null) { + emitAll(createTokenListLce(userWalletId, maybeContent, isCurrenciesLoading = true)) + } else { + emit(lceLoading()) + } + }, + ifContent = { content -> + emitAll(createTokenListLce(userWalletId, content, isCurrenciesLoading = false)) + }, + ifError = { error -> emit(error.lceError()) }, + ) + } } private fun createTokenList( userWalletId: UserWalletId, tokens: List, - ): Flow> { + ): EitherFlow { val operations = TokenListOperations( userWalletId = userWalletId, tokens = tokens, @@ -68,4 +88,22 @@ class GetTokenListUseCase( maybeTokenList.mapLeft(TokenListOperations.Error::mapToTokenListError) } } + + private fun createTokenListLce( + userWalletId: UserWalletId, + currencies: List, + isCurrenciesLoading: Boolean, + ): LceFlow { + val operations = TokenListOperations( + userWalletId = userWalletId, + tokens = currencies, + currenciesRepository = currenciesRepository, + ) + + return operations.getTokenListFlow().map { maybeTokenList -> + maybeTokenList + .mapLeft(TokenListOperations.Error::mapToTokenListError) + .toLce(isCurrenciesLoading) + } + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt new file mode 100644 index 0000000000..c358cd158e --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt @@ -0,0 +1,153 @@ +package com.tangem.domain.tokens.operations + +import arrow.core.* +import arrow.core.raise.recover +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.core.lce.lce +import com.tangem.domain.core.utils.lceError +import com.tangem.domain.core.utils.lceLoading +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.model.* +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 kotlinx.coroutines.flow.* + +internal class CurrenciesStatusesLceOperations( + private val currenciesRepository: CurrenciesRepository, + private val quotesRepository: QuotesRepository, + private val networksRepository: NetworksRepository, +) { + + fun getCurrenciesStatuses(userWalletId: UserWalletId): LceFlow> { + return getMultiCurrencyWalletCurrencies(userWalletId).transform transform@{ maybeCurrencies -> + val nonEmptyCurrencies = maybeCurrencies.fold( + ifLoading = { maybeContent -> + emit(createLoadingCurrenciesStatuses(maybeContent)) + return@transform + }, + ifContent = { content -> + val nonEmptyCurrencies = content.toNonEmptyListOrNull() + + if (nonEmptyCurrencies == null) { + emit(TokenListError.EmptyTokens.lceError()) + return@transform + } else { + nonEmptyCurrencies + } + }, + ifError = { error -> + emit(error.lceError()) + return@transform + }, + ) + + val (networks, currenciesIds) = getIds(nonEmptyCurrencies) + + combine( + getQuotes(currenciesIds), + getNetworksStatuses(userWalletId, networks), + ) { maybeQuotes, maybeNetworksStatuses -> + val statuses = createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes, maybeNetworksStatuses) + emit(statuses) + }.collect() + } + } + + private fun createLoadingCurrenciesStatuses( + maybeCurrencies: List?, + ): Lce> { + val nonEmptyCurrencies = maybeCurrencies?.toNonEmptyListOrNull() + + val statuses = if (nonEmptyCurrencies == null) { + lceLoading() + } else { + createCurrenciesStatuses( + nonEmptyCurrencies, + maybeNetworkStatuses = null, + maybeQuotes = null, + ) + } + + return statuses + } + + private fun getMultiCurrencyWalletCurrencies( + userWalletId: UserWalletId, + ): LceFlow> { + return currenciesRepository.getMultiCurrencyWalletCurrenciesUpdatesLce(userWalletId) + .map { maybeCurrencies -> + maybeCurrencies.mapError { TokenListError.DataError(it) } + } + } + + private fun createCurrenciesStatuses( + currencies: NonEmptyList, + maybeQuotes: Either>?, + maybeNetworkStatuses: Lce>?, + ): Lce> = lce { + isLoading.set(maybeNetworkStatuses == null) + + var quotesRetrievingFailed = false + + val networksStatuses = maybeNetworkStatuses?.bindOrNull()?.toNonEmptySetOrNull() + val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) { + quotesRetrievingFailed = true + null + } + + currencies.map { currency -> + val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId } + val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network } + + createCurrencyStatus(currency, quote, networkStatus, ignoreQuote = quotesRetrievingFailed) + } + } + + private fun createCurrencyStatus( + currency: CryptoCurrency, + quote: Quote?, + networkStatus: NetworkStatus?, + ignoreQuote: Boolean, + ): CryptoCurrencyStatus { + val currencyStatusOperations = CurrencyStatusOperations( + currency = currency, + quote = quote, + networkStatus = networkStatus, + ignoreQuote = ignoreQuote, + ) + + return currencyStatusOperations.createTokenStatus() + } + + private fun getQuotes(tokensIds: NonEmptySet): Flow>> { + return quotesRepository.getQuotesUpdates(tokensIds) + .map, Either>> { it.right() } + .catch { emit(TokenListError.DataError(it).left()) } + } + + private fun getNetworksStatuses( + userWalletId: UserWalletId, + networks: NonEmptySet, + ): LceFlow> { + return networksRepository.getNetworkStatusesUpdatesLce(userWalletId, networks) + .map { maybeStatuses -> + maybeStatuses.mapError { TokenListError.DataError(it) } + } + } + + private fun getIds(currencies: List): Pair, NonEmptySet> { + val currencyIdToNetworkId = currencies.associate { currency -> + currency.id to currency.network + } + val currenciesIds = currencyIdToNetworkId.keys.toNonEmptySetOrNull() + val networks = currencyIdToNetworkId.values.toNonEmptySetOrNull() + + requireNotNull(currenciesIds) { "Currencies IDs cannot be empty" } + requireNotNull(networks) { "Networks IDs cannot be empty" } + + return networks to currenciesIds + } +} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt index 48b5575b45..613246a85c 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt @@ -43,7 +43,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -59,7 +59,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(tokens = flowOf(DataError.NetworkError.NoInternetConnection.left())) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) @@ -79,7 +79,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -95,7 +95,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(isGrouped = flowOf(DataError.NetworkError.NoInternetConnection.left())) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) @@ -109,7 +109,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(isSortedByBalance = flowOf(DataError.NetworkError.NoInternetConnection.left())) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) @@ -134,7 +134,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 3) .toList() @@ -153,7 +153,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(isGrouped = flowOf(true.right())) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -175,7 +175,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -197,7 +197,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -212,7 +212,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(tokens = flowOf(emptyList().right())) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) @@ -225,7 +225,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(tokens = flowOf()) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) @@ -241,7 +241,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(statuses = flowOf()) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -256,7 +256,7 @@ internal class GetTokenListUseCaseTest { val useCase = getUseCase(statuses = flowOf(emptySet().right())) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) @@ -275,7 +275,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId) + val result = useCase.launch(userWalletId) .take(count = 2) .toList() @@ -293,7 +293,7 @@ internal class GetTokenListUseCaseTest { ) // When - val result = useCase(userWalletId).first() + val result = useCase.launch(userWalletId).first() // Then assertEquals(expectedResult, result) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt index 5342e0ece5..d4000aac56 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt @@ -161,7 +161,7 @@ internal class OrganizeTokensViewModel @Inject constructor( private fun bootstrapTokenList() { viewModelScope.launch(dispatchers.default) { - val maybeTokenList = getTokenListUseCase(userWalletId) + val maybeTokenList = getTokenListUseCase.launch(userWalletId) .first { it.getOrNull()?.totalFiatBalance !is TokenList.FiatBalance.Loading } maybeTokenList.fold( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 7432cbb84e..51fbfe6742 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -47,7 +47,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( val promoFlow = flow { emit(promoRepository.getChangellyPromoBanner()) } return combine( - flow = getTokenListUseCase(userWallet.walletId).conflate(), + flow = getTokenListUseCase.launch(userWallet.walletId).conflate(), flow2 = isReadyToShowRateAppUseCase().conflate(), flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(), flow4 = shouldShowSwapPromoWalletUseCase().conflate(), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 9185370945..53114d04ba 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -33,7 +33,7 @@ internal class MultiWalletTokenListSubscriber( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, ) { - override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase(userWallet.walletId) + override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase.launch(userWallet.walletId) override suspend fun onTokenListReceived(maybeTokenList: Either) { // TODO disabled for 5.7.2 because of potential critical From b601aea5c340caf99c3d2b39f64acd22e4767c65 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 16:01:41 +0800 Subject: [PATCH 23/98] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../datasource/config/ConfigManagerImpl.kt | 2 + .../datasource/config/models/JsonModels.kt | 2 + gradle/dependencies.toml | 2 +- .../DefaultBlockchainSDKFactory.kt | 67 ++++++++++++++----- .../blockchainsdk/config/ConfigStorage.kt | 18 ----- .../config/RuntimeConfigStorage.kt | 21 ------ .../BlockchainProviderTypesConverter.kt | 64 ++++++++++++++++++ .../BlockchainSDKConfigConverter.kt | 2 + .../di/BlockchainSDKFactoryModule.kt | 6 +- .../storage/DefaultRuntimeStore.kt | 20 ++++++ .../blockchainsdk/storage/RuntimeStore.kt | 17 +++++ .../tangem/blockchainsdk/utils/Blockchain.kt | 7 ++ 13 files changed, 172 insertions(+), 58 deletions(-) delete mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/ConfigStorage.kt delete mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/RuntimeConfigStorage.kt create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainProviderTypesConverter.kt create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/DefaultRuntimeStore.kt create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/RuntimeStore.kt diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index b098560072..367aa6465e 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit b0985600722953fae0a93bcbbc2a83930b9e0cbe +Subproject commit 367aa6465ec6ee7fdccce6cc7d7abeed5aad1111 diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt index 8ebac44c3b..ba04ed3f0f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt @@ -146,6 +146,8 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager { blockBookRest = accessTokens.bitcoin?.blockBookRest, ), algorand = GetBlockAccessToken(rest = accessTokens.algorand?.rest), + zkSyncEra = GetBlockAccessToken(jsonRpc = accessTokens.zksync?.jsonRPC), + polygonZkEvm = GetBlockAccessToken(jsonRpc = accessTokens.polygonZkevm?.jsonRPC), ) } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt index 2a79daad20..417d8f8b73 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt @@ -68,6 +68,8 @@ data class GetBlockAccessTokens( @Json(name = "dash") val dash: GetBlockToken?, @Json(name = "bitcoin") val bitcoin: GetBlockToken?, @Json(name = "algorand") val algorand: GetBlockToken?, + @Json(name = "polygon-zkevm") val polygonZkevm: GetBlockToken?, + @Json(name = "zksync") val zksync: GetBlockToken?, ) @JsonClass(generateAdapter = true) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index eab18a906d..80d6ea518d 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-576" +tangemBlockchainSdk = "develop-593" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-339" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt index 891dbc65b8..2a18ece1dc 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt @@ -1,32 +1,45 @@ package com.tangem.blockchainsdk import com.tangem.blockchain.common.AccountCreator +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.common.datastorage.BlockchainDataStorage import com.tangem.blockchain.common.logging.BlockchainSDKLogger -import com.tangem.blockchainsdk.config.ConfigStorage +import com.tangem.blockchain.common.network.providers.ProviderType +import com.tangem.blockchainsdk.converters.BlockchainProviderTypesConverter import com.tangem.blockchainsdk.converters.BlockchainSDKConfigConverter +import com.tangem.blockchainsdk.storage.RuntimeStore import com.tangem.datasource.asset.loader.AssetLoader import com.tangem.datasource.config.models.ConfigValueModel +import com.tangem.datasource.config.models.ProviderModel import com.tangem.libs.blockchain_sdk.BuildConfig +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +internal typealias BlockchainProvidersResponse = Map> +internal typealias BlockchainProviderTypes = Map> /** * Implementation of Blockchain SDK components factory * - * @property assetLoader asset loader - * @property configStorage config storage - * @property accountCreator account creator - * @property blockchainDataStorage blockchain data storage - * @property blockchainSDKLogger blockchain SDK logger + * @property assetLoader asset loader + * @property configStore blockchain sdk config store + * @property blockchainProviderTypesStore blockchain provider types store + * @property accountCreator account creator + * @property blockchainDataStorage blockchain data storage + * @property blockchainSDKLogger blockchain SDK logger * [REDACTED_AUTHOR] */ internal class DefaultBlockchainSDKFactory( private val assetLoader: AssetLoader, - private val configStorage: ConfigStorage, + private val configStore: RuntimeStore, + private val blockchainProviderTypesStore: RuntimeStore, private val accountCreator: AccountCreator, private val blockchainDataStorage: BlockchainDataStorage, private val blockchainSDKLogger: BlockchainSDKLogger, @@ -35,19 +48,22 @@ internal class DefaultBlockchainSDKFactory( override val walletManagerFactory: Flow by lazy(::createWalletManagerFactory) override suspend fun init() { - val configValueModel = assetLoader.load(CONFIG_FILE_NAME) ?: return - - configStorage.store( - config = BlockchainSDKConfigConverter.convert(value = configValueModel), - ) + coroutineScope { + updateBlockchainSDKConfig() + updateBlockchainProviderTypes() + } } override suspend fun getWalletManagerFactorySync(): WalletManagerFactory? = walletManagerFactory.firstOrNull() private fun createWalletManagerFactory(): Flow { - return configStorage.get().map { config -> + return combine( + flow = configStore.get(), + flow2 = blockchainProviderTypesStore.get(), + ) { config, blockchainProviderTypes -> WalletManagerFactory( config = config, + blockchainProviderTypes = blockchainProviderTypes, accountCreator = accountCreator, blockchainDataStorage = blockchainDataStorage, loggers = listOf(blockchainSDKLogger), @@ -55,8 +71,29 @@ internal class DefaultBlockchainSDKFactory( } } - private companion object { + private fun CoroutineScope.updateBlockchainSDKConfig() { + launch { + val config = assetLoader.load(fileName = CONFIG_FILE_NAME) ?: return@launch + configStore.store( + value = BlockchainSDKConfigConverter.convert(value = config), + ) + } + } + + private fun CoroutineScope.updateBlockchainProviderTypes() { + launch { + val providerTypes = assetLoader.load(fileName = PROVIDER_TYPES_FILE_NAME) + ?: return@launch + + blockchainProviderTypesStore.store( + value = BlockchainProviderTypesConverter.convert(providerTypes), + ) + } + } + + private companion object { const val CONFIG_FILE_NAME = "tangem-app-config/config_${BuildConfig.ENVIRONMENT}" + const val PROVIDER_TYPES_FILE_NAME = "tangem-app-config/providers_order" } } \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/ConfigStorage.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/ConfigStorage.kt deleted file mode 100644 index e9aaaf807b..0000000000 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/ConfigStorage.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.blockchainsdk.config - -import com.tangem.blockchain.common.BlockchainSdkConfig -import kotlinx.coroutines.flow.Flow - -/** - * Storage for [BlockchainSdkConfig] - * -[REDACTED_AUTHOR] - */ -internal interface ConfigStorage { - - /** Get flow of [BlockchainSdkConfig] */ - fun get(): Flow - - /** Store [BlockchainSdkConfig] */ - suspend fun store(config: BlockchainSdkConfig) -} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/RuntimeConfigStorage.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/RuntimeConfigStorage.kt deleted file mode 100644 index 0adb68868e..0000000000 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/RuntimeConfigStorage.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.blockchainsdk.config - -import com.tangem.blockchain.common.BlockchainSdkConfig -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow - -/** - * Runtime storage for [BlockchainSdkConfig] - * -[REDACTED_AUTHOR] - */ -internal class RuntimeConfigStorage : ConfigStorage { - - private val configFlow = MutableStateFlow(value = BlockchainSdkConfig()) - - override fun get(): Flow = configFlow - - override suspend fun store(config: BlockchainSdkConfig) { - configFlow.value = config - } -} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainProviderTypesConverter.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainProviderTypesConverter.kt new file mode 100644 index 0000000000..2a2e2a42f9 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainProviderTypesConverter.kt @@ -0,0 +1,64 @@ +package com.tangem.blockchainsdk.converters + +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.network.providers.ProviderType +import com.tangem.blockchainsdk.BlockchainProviderTypes +import com.tangem.blockchainsdk.BlockchainProvidersResponse +import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.datasource.config.models.ProviderModel +import com.tangem.utils.converter.Converter +import timber.log.Timber + +/** + * Converts [BlockchainProvidersResponse] to [BlockchainProviderTypes] + * +[REDACTED_AUTHOR] + */ +internal object BlockchainProviderTypesConverter : + Converter { + + override fun convert(value: BlockchainProvidersResponse): BlockchainProviderTypes { + return value.mapNotNull { (networkId, blockchainProviders) -> + val blockchain = Blockchain.fromNetworkId(networkId) ?: return@mapNotNull null + + val providerTypes = blockchainProviders.mapNotNull { provider -> + when (provider) { + is ProviderModel.Public -> ProviderType.Public(url = provider.url) + is ProviderModel.Private -> createPrivateProviderType(blockchain = blockchain, name = provider.name) + ProviderModel.UnsupportedType -> { + Timber.e("$blockchain provider type is not supported") + null + } + } + } + + blockchain to providerTypes + } + .toMap() + } + + @Suppress("CyclomaticComplexMethod") + private fun createPrivateProviderType(blockchain: Blockchain, name: String): ProviderType? { + return when (name) { + "blockchair" -> ProviderType.BitcoinLike.Blockchair + "blockcypher" -> ProviderType.BitcoinLike.Blockcypher + "adalite" -> ProviderType.Cardano.Adalite + "tangemRosetta" -> ProviderType.Cardano.Rosetta + "fireAcademy" -> ProviderType.Chia.FireAcademy + "tangemChia" -> ProviderType.Chia.Tangem + "infura" -> ProviderType.EthereumLike.Infura + "getblock" -> ProviderType.GetBlock + "arkhiaHedera" -> ProviderType.Hedera.Arkhia + "kaspa" -> ProviderType.Kaspa.SecondaryAPI + "nownodes" -> ProviderType.NowNodes + "quicknode" -> ProviderType.QuickNode + "solana" -> ProviderType.Solana.Official + "ton" -> ProviderType.Ton.TonCentral + "tron" -> ProviderType.Tron.TronGrid + else -> { + Timber.e("$blockchain private provider ($name) is not supported") + null + } + } + } +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt index d7551a2eea..17d38bd0c6 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt @@ -77,6 +77,8 @@ internal object BlockchainSDKConfigConverter : Converter(defaultValue: T) : RuntimeStore { + + private val flow = MutableStateFlow(value = defaultValue) + + override fun get(): StateFlow = flow + + override suspend fun store(value: T) { + flow.value = value + } +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/RuntimeStore.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/RuntimeStore.kt new file mode 100644 index 0000000000..a3de160654 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/RuntimeStore.kt @@ -0,0 +1,17 @@ +package com.tangem.blockchainsdk.storage + +import kotlinx.coroutines.flow.StateFlow + +/** + * Runtime store + * +[REDACTED_AUTHOR] + */ +internal interface RuntimeStore { + + /** Get flow of elements [T] */ + fun get(): StateFlow + + /** Store [value] */ + suspend fun store(value: T) +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index 2d011e9af9..cd181cb4c1 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -114,6 +114,8 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "taraxa/test" -> Blockchain.TaraxaTestnet "base" -> Blockchain.Base "base/test" -> Blockchain.BaseTestnet + "koinos" -> Blockchain.Koinos + "koinos/test" -> Blockchain.KoinosTestnet else -> null } } @@ -229,6 +231,8 @@ fun Blockchain.toNetworkId(): String { Blockchain.TaraxaTestnet -> "taraxa/test" Blockchain.Base -> "base" Blockchain.BaseTestnet -> "base/test" + Blockchain.Koinos -> "koinos" + Blockchain.KoinosTestnet -> "koinos/test" } } @@ -302,6 +306,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Flare, Blockchain.FlareTestnet -> "flare-networks" Blockchain.Taraxa, Blockchain.TaraxaTestnet -> "taraxa" Blockchain.Base, Blockchain.BaseTestnet -> "base" + Blockchain.Koinos, Blockchain.KoinosTestnet -> "koinos" } } @@ -332,4 +337,6 @@ private val excludedBlockchains = listOf( Blockchain.Nexa, Blockchain.NexaTestnet, Blockchain.Radiant, + Blockchain.Koinos, + Blockchain.KoinosTestnet, ) \ No newline at end of file From 9d062102b9880e6fa75b782789f9b583cef42527 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 16:49:00 +0200 Subject: [PATCH 24/98] Updated on 2026-08-14 --- .../kotlin/com/tangem/common/BaseTestCase.kt | 1 + .../com/tangem/screens/DisclaimerScreen.kt | 17 +++ .../com/tangem/screens/StoriesScreen.kt | 8 +- .../kotlin/com/tangem/screens/WalletScreen.kt | 11 ++ .../kotlin/com/tangem/tests/MainScreenTest.kt | 39 ++++++ .../tangem/tap/common/compose/resources/C.kt | 9 -- .../domain/sdk/impl/MockTangemSdkManager.kt | 6 +- .../tap/domain/sdk/mocks/MockProvider.kt | 20 +++ .../com/tangem/tap/domain/sdk/mocks/Mocks.kt | 8 ++ .../domain/sdk/mocks/wallet/WalletMocks.kt | 127 ++++++++++++++++++ .../domain/sdk/mocks/wallet2/Wallet2Mocks.kt | 10 ++ .../features/home/compose/StoriesScreen.kt | 4 +- .../home/compose/views/HomeButtons.kt | 6 +- .../java/com/tangem/core/ui/test/TestTags.kt | 9 ++ .../presentation/wallet/ui/WalletScreen.kt | 4 +- gradle/dependencies.toml | 2 +- tangem-android-tools | 2 +- 17 files changed, 259 insertions(+), 24 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/tangem/screens/DisclaimerScreen.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/screens/WalletScreen.kt create mode 100644 app/src/androidTest/kotlin/com/tangem/tests/MainScreenTest.kt delete mode 100644 app/src/main/java/com/tangem/tap/common/compose/resources/C.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/sdk/mocks/Mocks.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet/WalletMocks.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet2/Wallet2Mocks.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/test/TestTags.kt diff --git a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt index 4b3278bc9f..7f38b804c2 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt @@ -3,6 +3,7 @@ package com.tangem.common import android.Manifest import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.espresso.intent.Intents +import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.rule.GrantPermissionRule import com.kaspersky.components.composesupport.config.withComposeSupport diff --git a/app/src/androidTest/kotlin/com/tangem/screens/DisclaimerScreen.kt b/app/src/androidTest/kotlin/com/tangem/screens/DisclaimerScreen.kt new file mode 100644 index 0000000000..51b94ab791 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/DisclaimerScreen.kt @@ -0,0 +1,17 @@ +package com.tangem.screens + +import com.kaspersky.kaspresso.screens.KScreen +import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment +import com.tangem.wallet.R +import io.github.kakaocup.kakao.text.KButton + +object DisclaimerScreen : KScreen(){ + + override val layoutId = R.layout.fragment_disclaimer + + override val viewClass = DisclaimerFragment::class.java + + val acceptButton: KButton = KButton { + withId(R.id.btn_accept) + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/StoriesScreen.kt b/app/src/androidTest/kotlin/com/tangem/screens/StoriesScreen.kt index 38bdbce846..cda17d33c2 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/StoriesScreen.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/StoriesScreen.kt @@ -1,7 +1,7 @@ package com.tangem.screens import androidx.compose.ui.test.SemanticsNodeInteractionsProvider -import com.tangem.tap.common.compose.resources.C +import com.tangem.core.ui.test.TestTags import com.tangem.wallet.R import io.github.kakaocup.compose.node.element.ComposeScreen import io.github.kakaocup.compose.node.element.KNode @@ -11,15 +11,15 @@ import io.github.kakaocup.kakao.text.KButton class StoriesScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : ComposeScreen( semanticsProvider = semanticsProvider, - viewBuilderAction = { hasTestTag(C.Tag.STORIES_SCREEN) } + viewBuilderAction = { hasTestTag(TestTags.STORIES_SCREEN) } ) { val scanButton: KNode = child { - hasTestTag(C.Tag.STORIES_SCREEN_SCAN_BUTTON) + hasTestTag(TestTags.STORIES_SCREEN_SCAN_BUTTON) } val orderButton: KNode = child { - hasTestTag(C.Tag.STORIES_SCREEN_ORDER_BUTTON) + hasTestTag(TestTags.STORIES_SCREEN_ORDER_BUTTON) } val enableNFCAlert: KView = KView { diff --git a/app/src/androidTest/kotlin/com/tangem/screens/WalletScreen.kt b/app/src/androidTest/kotlin/com/tangem/screens/WalletScreen.kt new file mode 100644 index 0000000000..0988625537 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/WalletScreen.kt @@ -0,0 +1,11 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.core.ui.test.TestTags +import io.github.kakaocup.compose.node.element.ComposeScreen + +class WalletScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(TestTags.WALLET_SCREEN) } + ) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/tests/MainScreenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/MainScreenTest.kt new file mode 100644 index 0000000000..dcebde4556 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/MainScreenTest.kt @@ -0,0 +1,39 @@ +package com.tangem.tests + +import com.tangem.common.BaseTestCase +import com.tangem.screens.DisclaimerScreen +import com.tangem.screens.StoriesScreen +import com.tangem.screens.WalletScreen +import dagger.hilt.android.testing.HiltAndroidTest +import io.github.kakaocup.compose.node.element.ComposeScreen +import org.junit.Test + +@HiltAndroidTest +class MainScreenTest : BaseTestCase() { + + @Test + fun goToMain() = + setupHooks().run { + ComposeScreen.onComposeScreen(composeTestRule) { + step("Click on \"Scan\" button") { + scanButton { + assertIsDisplayed() + performClick() + } + } + } + DisclaimerScreen { + step("Click on \"Accept\" button") { + acceptButton { + isVisible() + click() + } + } + } + ComposeScreen.onComposeScreen(composeTestRule) { + step("Make sure wallet screen is visible") { + assertIsDisplayed() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/resources/C.kt b/app/src/main/java/com/tangem/tap/common/compose/resources/C.kt deleted file mode 100644 index ca77ba62d2..0000000000 --- a/app/src/main/java/com/tangem/tap/common/compose/resources/C.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.tap.common.compose.resources - -object C { - object Tag { - const val STORIES_SCREEN = "STORIES_SCREEN_CONTAINER" - const val STORIES_SCREEN_SCAN_BUTTON = "STORIES_SCREEN_SCAN_BUTTON" - const val STORIES_SCREEN_ORDER_BUTTON = "STORIES_SCREEN_ORDER_BUTTON" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt index 649707aee5..8279c72670 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt @@ -15,6 +15,7 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.tap.domain.sdk.TangemSdkManager +import com.tangem.tap.domain.sdk.mocks.MockProvider import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -25,7 +26,7 @@ class MockTangemSdkManager( ) : TangemSdkManager { override val canUseBiometry: Boolean - get() = TODO() + get() = false override val needEnrollBiometrics: Boolean get() = TODO() @@ -44,7 +45,7 @@ class MockTangemSdkManager( messageRes: Int?, allowsRequestAccessCodeFromRepository: Boolean, ): CompletionResult { - TODO() + return CompletionResult.Success(MockProvider.getScanResponse()) } override suspend fun createProductWallet( @@ -135,7 +136,6 @@ class MockTangemSdkManager( @Suppress("MagicNumber") override fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) { - TODO() } @Deprecated("TangemSdkManager shouldn't returns a string from resources") diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt new file mode 100644 index 0000000000..0d0c1eabed --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.domain.sdk.mocks + +import com.tangem.domain.models.scan.ProductType +import com.tangem.tap.domain.sdk.mocks.wallet.WalletMocks +import com.tangem.tap.domain.sdk.mocks.wallet2.Wallet2Mocks + +object MockProvider { + + var productType: ProductType = ProductType.Wallet + + fun getScanResponse() = getMocks(productType).scanResponse + + private fun getMocks(productType: ProductType): Mocks { + return when (productType) { + ProductType.Wallet -> WalletMocks + ProductType.Wallet2 -> Wallet2Mocks + else -> TODO() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/Mocks.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/Mocks.kt new file mode 100644 index 0000000000..a7de03220a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/Mocks.kt @@ -0,0 +1,8 @@ +package com.tangem.tap.domain.sdk.mocks + +import com.tangem.domain.models.scan.ScanResponse + +interface Mocks { + + val scanResponse: ScanResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet/WalletMocks.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet/WalletMocks.kt new file mode 100644 index 0000000000..2157f6fd60 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet/WalletMocks.kt @@ -0,0 +1,127 @@ +package com.tangem.tap.domain.sdk.mocks.wallet + +import com.tangem.common.card.CardWallet +import com.tangem.common.card.EllipticCurve +import com.tangem.common.card.EncryptionMode +import com.tangem.common.card.FirmwareVersion +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.attestation.Attestation +import com.tangem.tap.domain.sdk.mocks.Mocks +import java.util.Date + +object WalletMocks : Mocks { + + private val cardDto = CardDTO( + cardId = "AC05000000086747", + batchId = "AC05", + cardPublicKey = byteArrayOf(2, -120, -3, -32, -122, -127, -120, -104, 59, 72, 76, 114, 94, 75, -37, -55, 55, 99, 66, 123, 85, -87, 80, 106, 105, -116, 87, -83, -12, 70, 108, -68, -39), + firmwareVersion = CardDTO.FirmwareVersion( + major = 4, + minor = 52, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + manufacturer = CardDTO.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1649635200000), + signature = byteArrayOf(-20, 98, -101, 94, -23, 73, 122, -21, 74, 76, 79, -55, -102, -62, -30, 44, -38, 118, 75, 121, -36, 118, -62, 60, -38, -63, 33, -14, -98, -69, 112, 22, 48, -43, 47, 65, -61, -56, 38, -94, -45, 44, 95, -22, 6, -31, -40, -25, 42, 38, 94, -99, 71, 98, -33, 27, -102, 30, 52, -7, 51, -27, 84, 66), + ), + issuer = CardDTO.Issuer( + name = "TANGEM AG", + publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2), + ), + settings = CardDTO.Settings( + securityDelay = 15000, + maxWalletsCount = 20, + isSettingAccessCodeAllowed = false, + isSettingPasscodeAllowed = false, + isResettingUserCodesAllowed = true, + isLinkedTerminalEnabled = true, + isBackupAllowed = true, + supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None), + isFilesAllowed = true, + isHDWalletAllowed = true, + isKeysImportAllowed = false, + ), + userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true), + linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None, + isAccessCodeSet = false, + isPasscodeSet = false, + supportedCurves = listOf( + EllipticCurve.Secp256k1, + EllipticCurve.Ed25519, + EllipticCurve.Secp256r1, + EllipticCurve.Bls12381G2, + EllipticCurve.Bls12381G2Aug, + EllipticCurve.Bls12381G2Pop, + EllipticCurve.Bip0340, + ), + wallets = listOf( + CardDTO.Wallet( + publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), + chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14), + curve = EllipticCurve.Secp256k1, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 0, + hasBackup = false, + derivedKeys = mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + ), + DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + ), + ), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), + chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14), + ), + isImported = false, + ), + CardDTO.Wallet( + publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109), + chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6), + curve = EllipticCurve.Ed25519, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 1, + hasBackup = false, + derivedKeys = emptyMap(), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109), + chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6), + ), + isImported = false, + ), + ), + attestation = Attestation( + cardKeyAttestation = Attestation.Status.Verified, + walletKeysAttestation = Attestation.Status.Skipped, + firmwareAttestation = Attestation.Status.Skipped, + cardUniquenessAttestation = Attestation.Status.Skipped, + ), + backupStatus = CardDTO.BackupStatus.NoBackup, + ) + + override val scanResponse = ScanResponse( + card = cardDto, + productType = ProductType.Wallet, + walletData = null, + secondTwinPublicKey = null, + derivedKeys = emptyMap(), + primaryCard = null, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet2/Wallet2Mocks.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet2/Wallet2Mocks.kt new file mode 100644 index 0000000000..86b865896f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet2/Wallet2Mocks.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.domain.sdk.mocks.wallet2 + +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.tap.domain.sdk.mocks.Mocks + +object Wallet2Mocks : Mocks { + + override val scanResponse: ScanResponse + get() = TODO("Not yet implemented") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt index bd3f2a5dde..844d5e6663 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.common.compose.resources.C +import com.tangem.core.ui.test.TestTags import com.tangem.tap.features.home.compose.content.* import com.tangem.tap.features.home.compose.views.HomeButtons import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton @@ -57,7 +57,7 @@ fun StoriesScreen( } StoriesScreenContent( - modifier = Modifier.fillMaxSize().testTag(C.Tag.STORIES_SCREEN), + modifier = Modifier.fillMaxSize().testTag(TestTags.STORIES_SCREEN), config = StoriesScreenContentConfig( storiesSize = state.stories.lastIndex, currentStoryIndex = currentStoryIndex, diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt index 75d308e2b7..3a9ebaf1bf 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import com.tangem.core.ui.components.SpacerW12 import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.common.compose.resources.C +import com.tangem.core.ui.test.TestTags import com.tangem.wallet.R @Composable @@ -33,7 +33,7 @@ internal fun HomeButtons( ScanCardButton( modifier = Modifier .weight(weight = 1f) - .testTag(C.Tag.STORIES_SCREEN_SCAN_BUTTON), + .testTag(TestTags.STORIES_SCREEN_SCAN_BUTTON), showProgress = btnScanStateInProgress, onClick = onScanButtonClick, ) @@ -41,7 +41,7 @@ internal fun HomeButtons( OrderCardButton( modifier = Modifier .weight(weight = 1f) - .testTag(C.Tag.STORIES_SCREEN_ORDER_BUTTON), + .testTag(TestTags.STORIES_SCREEN_ORDER_BUTTON), onClick = onShopButtonClick, ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/TestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/TestTags.kt new file mode 100644 index 0000000000..8548896b31 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/test/TestTags.kt @@ -0,0 +1,9 @@ +package com.tangem.core.ui.test + +object TestTags { + const val STORIES_SCREEN = "STORIES_SCREEN_CONTAINER" + const val STORIES_SCREEN_SCAN_BUTTON = "STORIES_SCREEN_SCAN_BUTTON" + const val STORIES_SCREEN_ORDER_BUTTON = "STORIES_SCREEN_ORDER_BUTTON" + + const val WALLET_SCREEN = "WALLET_SCREEN_CONTAINER" +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 9819f09790..cfeccc9db0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.unit.Dp @@ -39,6 +40,7 @@ import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBotto import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.test.TestTags import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.* import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder @@ -133,7 +135,7 @@ private fun WalletContent( .padding(horizontal = horizontalPadding) LazyColumn( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize().testTag(TestTags.WALLET_SCREEN), contentPadding = PaddingValues( top = TangemTheme.dimens.spacing8, bottom = TangemTheme.dimens.spacing92, diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 80d6ea518d..6b9028a0de 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -87,7 +87,7 @@ leakcanary = "2.13" # region Tangem tangemBlockchainSdk = "develop-593" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-339" +tangemCardSdk = "develop-344" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem diff --git a/tangem-android-tools b/tangem-android-tools index 0e4934995b..d8e6da9ab3 160000 --- a/tangem-android-tools +++ b/tangem-android-tools @@ -1 +1 @@ -Subproject commit 0e4934995b8e7c69862ef80362e86bd166bd1288 +Subproject commit d8e6da9ab3a0442657822352cb6274237d88e77d From c642d211b1a5f82842b63bd5184ab974a555c015 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2024 09:49:55 +0800 Subject: [PATCH 25/98] Updated on 2026-08-14 --- .../api/tangemTech/TangemTechServiceApi.kt | 19 +++++ .../com/tangem/datasource/di/NetworkModule.kt | 29 ++++++++ gradle/dependencies.toml | 2 +- .../blockchainsdk/BlockchainSDKFactory.kt | 4 - .../DefaultBlockchainSDKFactory.kt | 73 ++++++++++--------- .../WalletManagerFactoryCreator.kt | 37 ++++++++++ .../di/BlockchainSDKFactoryModule.kt | 29 ++++++-- .../BlockchainProvidersResponseLoader.kt | 57 +++++++++++++++ .../{storage => store}/DefaultRuntimeStore.kt | 2 +- .../{storage => store}/RuntimeStore.kt | 2 +- 10 files changed, 206 insertions(+), 48 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechServiceApi.kt create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/BlockchainProvidersResponseLoader.kt rename libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/{storage => store}/DefaultRuntimeStore.kt (91%) rename libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/{storage => store}/RuntimeStore.kt (85%) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechServiceApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechServiceApi.kt new file mode 100644 index 0000000000..107db49e49 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechServiceApi.kt @@ -0,0 +1,19 @@ +package com.tangem.datasource.api.tangemTech + +import com.tangem.datasource.config.models.ProviderModel +import retrofit2.http.GET +import retrofit2.http.Header + +/** + * Tangem Tech API for app services + * +[REDACTED_AUTHOR] + */ +interface TangemTechServiceApi { + + @GET("v1/networks") + suspend fun getBlockchainProviders( + @Header("card_public_key") cardPublicKey: String, + @Header("card_id") cardId: String, + ): Map> +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index e52008bbc5..d229d1dbf1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -6,6 +6,7 @@ import com.tangem.datasource.BuildConfig import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.TangemTechServiceApi import com.tangem.datasource.utils.RequestHeader.* import com.tangem.datasource.utils.addHeaders import com.tangem.datasource.utils.addLoggers @@ -19,6 +20,7 @@ import dagger.hilt.components.SingletonComponent import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory +import java.util.concurrent.TimeUnit import javax.inject.Singleton @Module @@ -96,6 +98,33 @@ class NetworkModule { .create(TangemTechApi::class.java) } + @Provides + @Singleton + fun provideTangemTechServiceApi( + @NetworkMoshi moshi: Moshi, + @ApplicationContext context: Context, + appVersionProvider: AppVersionProvider, + ): TangemTechServiceApi { + return Retrofit.Builder() + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .addCallAdapterFactory(ApiResponseCallAdapterFactory.create()) + .baseUrl(PROD_TANGEM_TECH_BASE_URL) + .client( + OkHttpClient.Builder() + .callTimeout(timeout = 5, unit = TimeUnit.SECONDS) + .addHeaders( + CacheControlHeader, + AppVersionPlatformHeaders(appVersionProvider), + // TODO("refactor header init") get auth data after biometric auth to avoid race condition + // AuthenticationHeader(authProvider), + ) + .addLoggers(context) + .build(), + ) + .build() + .create(TangemTechServiceApi::class.java) + } + private companion object { const val PROD_EXPRESS_BASE_URL = "https://express.tangem.com/v1/" const val DEV_EXPRESS_BASE_URL = "[REDACTED_ENV_URL]" diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 6b9028a0de..4f36bd773a 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-593" +tangemBlockchainSdk = "develop-598" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-344" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/BlockchainSDKFactory.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/BlockchainSDKFactory.kt index c38d226ee4..991f599242 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/BlockchainSDKFactory.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/BlockchainSDKFactory.kt @@ -1,7 +1,6 @@ package com.tangem.blockchainsdk import com.tangem.blockchain.common.WalletManagerFactory -import kotlinx.coroutines.flow.Flow /** * Blockchain SDK components factory @@ -10,9 +9,6 @@ import kotlinx.coroutines.flow.Flow */ interface BlockchainSDKFactory { - /** Flow of [WalletManagerFactory] */ - val walletManagerFactory: Flow - /** Initialize components */ suspend fun init() diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt index 2a18ece1dc..406be2d877 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt @@ -1,25 +1,21 @@ package com.tangem.blockchainsdk -import com.tangem.blockchain.common.AccountCreator import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.blockchain.common.datastorage.BlockchainDataStorage -import com.tangem.blockchain.common.logging.BlockchainSDKLogger import com.tangem.blockchain.common.network.providers.ProviderType import com.tangem.blockchainsdk.converters.BlockchainProviderTypesConverter import com.tangem.blockchainsdk.converters.BlockchainSDKConfigConverter -import com.tangem.blockchainsdk.storage.RuntimeStore +import com.tangem.blockchainsdk.loader.BlockchainProvidersResponseLoader +import com.tangem.blockchainsdk.store.RuntimeStore import com.tangem.datasource.asset.loader.AssetLoader import com.tangem.datasource.config.models.ConfigValueModel import com.tangem.datasource.config.models.ProviderModel import com.tangem.libs.blockchain_sdk.BuildConfig -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.launch +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import timber.log.Timber internal typealias BlockchainProvidersResponse = Map> internal typealias BlockchainProviderTypes = Map> @@ -27,25 +23,26 @@ internal typealias BlockchainProviderTypes = Map> /** * Implementation of Blockchain SDK components factory * - * @property assetLoader asset loader - * @property configStore blockchain sdk config store - * @property blockchainProviderTypesStore blockchain provider types store - * @property accountCreator account creator - * @property blockchainDataStorage blockchain data storage - * @property blockchainSDKLogger blockchain SDK logger + * @property assetLoader asset loader + * @property blockchainProvidersResponseLoader blockchain providers response loader + * @property configStore blockchain sdk config store + * @property blockchainProviderTypesStore blockchain provider types store + * @property walletManagerFactoryCreator wallet manager factory creator * [REDACTED_AUTHOR] */ internal class DefaultBlockchainSDKFactory( private val assetLoader: AssetLoader, + private val blockchainProvidersResponseLoader: BlockchainProvidersResponseLoader, private val configStore: RuntimeStore, private val blockchainProviderTypesStore: RuntimeStore, - private val accountCreator: AccountCreator, - private val blockchainDataStorage: BlockchainDataStorage, - private val blockchainSDKLogger: BlockchainSDKLogger, + private val walletManagerFactoryCreator: WalletManagerFactoryCreator, + dispatchers: CoroutineDispatcherProvider, ) : BlockchainSDKFactory { - override val walletManagerFactory: Flow by lazy(::createWalletManagerFactory) + private val walletManagerFactory: Flow by lazy(::createWalletManagerFactory) + + private val mainScope = CoroutineScope(dispatchers.main) override suspend fun init() { coroutineScope { @@ -56,24 +53,25 @@ internal class DefaultBlockchainSDKFactory( override suspend fun getWalletManagerFactorySync(): WalletManagerFactory? = walletManagerFactory.firstOrNull() - private fun createWalletManagerFactory(): Flow { + private fun createWalletManagerFactory(): Flow { return combine( flow = configStore.get(), flow2 = blockchainProviderTypesStore.get(), - ) { config, blockchainProviderTypes -> - WalletManagerFactory( - config = config, - blockchainProviderTypes = blockchainProviderTypes, - accountCreator = accountCreator, - blockchainDataStorage = blockchainDataStorage, - loggers = listOf(blockchainSDKLogger), - ) - } + transform = walletManagerFactoryCreator::create, + ) + .stateIn(scope = mainScope, started = SharingStarted.Eagerly, initialValue = null) } private fun CoroutineScope.updateBlockchainSDKConfig() { launch { - val config = assetLoader.load(fileName = CONFIG_FILE_NAME) ?: return@launch + val config = assetLoader.load(fileName = CONFIG_FILE_NAME) + + if (config == null) { + Timber.e("Error loading BlockchainSDKConfig") + return@launch + } + + Timber.d("Update BlockchainSDKConfig") configStore.store( value = BlockchainSDKConfigConverter.convert(value = config), @@ -83,17 +81,22 @@ internal class DefaultBlockchainSDKFactory( private fun CoroutineScope.updateBlockchainProviderTypes() { launch { - val providerTypes = assetLoader.load(fileName = PROVIDER_TYPES_FILE_NAME) - ?: return@launch + val response = blockchainProvidersResponseLoader.load() + + if (response == null) { + Timber.e("Error loading BlockchainProviderTypes") + return@launch + } + + Timber.d("Update BlockchainProviderTypes") blockchainProviderTypesStore.store( - value = BlockchainProviderTypesConverter.convert(providerTypes), + value = BlockchainProviderTypesConverter.convert(response), ) } } private companion object { const val CONFIG_FILE_NAME = "tangem-app-config/config_${BuildConfig.ENVIRONMENT}" - const val PROVIDER_TYPES_FILE_NAME = "tangem-app-config/providers_order" } } \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt new file mode 100644 index 0000000000..feae18a4e4 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt @@ -0,0 +1,37 @@ +package com.tangem.blockchainsdk + +import com.tangem.blockchain.common.AccountCreator +import com.tangem.blockchain.common.BlockchainSdkConfig +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.blockchain.common.datastorage.BlockchainDataStorage +import com.tangem.blockchain.common.logging.BlockchainSDKLogger +import timber.log.Timber +import javax.inject.Inject + +/** + * Creator of [WalletManagerFactory] + * + * @property accountCreator account creator + * @property blockchainDataStorage blockchain data storage + * @property blockchainSDKLogger blockchain SDK logger + * +[REDACTED_AUTHOR] + */ +internal class WalletManagerFactoryCreator @Inject constructor( + private val accountCreator: AccountCreator, + private val blockchainDataStorage: BlockchainDataStorage, + private val blockchainSDKLogger: BlockchainSDKLogger, +) { + + fun create(config: BlockchainSdkConfig, blockchainProviderTypes: BlockchainProviderTypes): WalletManagerFactory { + Timber.d("Create WalletManagerFactory") + + return WalletManagerFactory( + config = config, + blockchainProviderTypes = blockchainProviderTypes, + accountCreator = accountCreator, + blockchainDataStorage = blockchainDataStorage, + loggers = listOf(blockchainSDKLogger), + ) + } +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt index f3df5595b6..3ebbe75b92 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt @@ -4,13 +4,16 @@ import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.logging.BlockchainSDKLogger import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.blockchainsdk.DefaultBlockchainSDKFactory +import com.tangem.blockchainsdk.WalletManagerFactoryCreator import com.tangem.blockchainsdk.accountcreator.DefaultAccountCreator import com.tangem.blockchainsdk.datastorage.DefaultBlockchainDataStorage -import com.tangem.blockchainsdk.storage.DefaultRuntimeStore +import com.tangem.blockchainsdk.loader.BlockchainProvidersResponseLoader +import com.tangem.blockchainsdk.store.DefaultRuntimeStore import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.asset.loader.AssetLoader import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -25,15 +28,29 @@ internal object BlockchainSDKFactoryModule { @Singleton fun provideBlockchainSDKFactory( assetLoader: AssetLoader, + blockchainProvidersResponseLoader: BlockchainProvidersResponseLoader, + walletManagerFactoryCreator: WalletManagerFactoryCreator, + dispatchers: CoroutineDispatcherProvider, + ): BlockchainSDKFactory { + return DefaultBlockchainSDKFactory( + assetLoader = assetLoader, + blockchainProvidersResponseLoader = blockchainProvidersResponseLoader, + configStore = DefaultRuntimeStore(defaultValue = BlockchainSdkConfig()), + blockchainProviderTypesStore = DefaultRuntimeStore(defaultValue = emptyMap()), + walletManagerFactoryCreator = walletManagerFactoryCreator, + dispatchers = dispatchers, + ) + } + + @Provides + @Singleton + fun provideWalletManagerFactoryCreator( authProvider: AuthProvider, tangemTechApi: TangemTechApi, appPreferencesStore: AppPreferencesStore, blockchainSDKLogger: BlockchainSDKLogger, - ): BlockchainSDKFactory { - return DefaultBlockchainSDKFactory( - assetLoader = assetLoader, - configStore = DefaultRuntimeStore(defaultValue = BlockchainSdkConfig()), - blockchainProviderTypesStore = DefaultRuntimeStore(defaultValue = emptyMap()), + ): WalletManagerFactoryCreator { + return WalletManagerFactoryCreator( accountCreator = DefaultAccountCreator(authProvider, tangemTechApi), blockchainDataStorage = DefaultBlockchainDataStorage(appPreferencesStore), blockchainSDKLogger = blockchainSDKLogger, diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/BlockchainProvidersResponseLoader.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/BlockchainProvidersResponseLoader.kt new file mode 100644 index 0000000000..7613213ba4 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/BlockchainProvidersResponseLoader.kt @@ -0,0 +1,57 @@ +package com.tangem.blockchainsdk.loader + +import com.tangem.blockchainsdk.BlockchainProvidersResponse +import com.tangem.datasource.api.common.AuthProvider +import com.tangem.datasource.api.tangemTech.TangemTechServiceApi +import com.tangem.datasource.asset.loader.AssetLoader +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runCatching +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Loader of [BlockchainProvidersResponse] + * + * @property tangemTechServiceApi tangem tech api + * @property authProvider auth provider + * @property assetLoader asset loader for local config loading + * @property dispatchers dispatchers + * +[REDACTED_AUTHOR] + */ +@Singleton +internal class BlockchainProvidersResponseLoader @Inject constructor( + private val tangemTechServiceApi: TangemTechServiceApi, + private val authProvider: AuthProvider, + private val assetLoader: AssetLoader, + private val dispatchers: CoroutineDispatcherProvider, +) { + + /** Load [BlockchainProvidersResponse] */ + suspend fun load(): BlockchainProvidersResponse? { + return runCatching(dispatcher = dispatchers.io, block = ::loadRemote) + .fold( + onSuccess = { it }, + onFailure = { + Timber.e(it, "Failed to load blockchain provider types from backend") + loadLocal() + }, + ) + } + + private suspend fun loadRemote(): BlockchainProvidersResponse { + return tangemTechServiceApi.getBlockchainProviders( + cardPublicKey = authProvider.getCardPublicKey(), + cardId = authProvider.getCardId(), + ) + } + + private suspend fun loadLocal(): BlockchainProvidersResponse? { + return assetLoader.load(fileName = PROVIDER_TYPES_FILE_NAME) + } + + private companion object { + const val PROVIDER_TYPES_FILE_NAME = "tangem-app-config/providers_order" + } +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/DefaultRuntimeStore.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/store/DefaultRuntimeStore.kt similarity index 91% rename from libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/DefaultRuntimeStore.kt rename to libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/store/DefaultRuntimeStore.kt index e670704e85..4278918e98 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/DefaultRuntimeStore.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/store/DefaultRuntimeStore.kt @@ -1,4 +1,4 @@ -package com.tangem.blockchainsdk.storage +package com.tangem.blockchainsdk.store import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/RuntimeStore.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/store/RuntimeStore.kt similarity index 85% rename from libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/RuntimeStore.kt rename to libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/store/RuntimeStore.kt index a3de160654..2969e42576 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/RuntimeStore.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/store/RuntimeStore.kt @@ -1,4 +1,4 @@ -package com.tangem.blockchainsdk.storage +package com.tangem.blockchainsdk.store import kotlinx.coroutines.flow.StateFlow From c66a6f7ecb8a190c6b9c11d002abbc6996459973 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Apr 2024 12:46:03 +0400 Subject: [PATCH 26/98] Updated on 2026-08-14 --- .../configs/feature_toggles_config.json | 4 ++ .../feature/wallet/di/FeatureTogglesModule.kt | 20 +++++++ .../featuretoggle/WalletFeatureToggles.kt | 11 ++++ .../organizetokens/OrganizeTokensViewModel.kt | 60 ++++++++++++++----- .../implementors/MultiWalletContentLoader.kt | 3 + .../MultiWalletContentLoaderFactory.kt | 3 + .../subscribers/BasicTokenListSubscriber.kt | 30 ++++++---- .../MultiWalletTokenListSubscriber.kt | 28 +++++---- .../SingleWalletWithTokenListSubscriber.kt | 8 ++- 9 files changed, 128 insertions(+), 39 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/FeatureTogglesModule.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggle/WalletFeatureToggles.kt diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index c814055bdc..5598afe138 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -22,5 +22,9 @@ { "name": "WC_SOLANA_TX_SIGN_ENABLED", "version": "5.9.0" + }, + { + "name": "TOKEN_LIST_LCE_ENABLED", + "version": "5.10.0" } ] diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/FeatureTogglesModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/FeatureTogglesModule.kt new file mode 100644 index 0000000000..a358685961 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/FeatureTogglesModule.kt @@ -0,0 +1,20 @@ +package com.tangem.feature.wallet.di + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object FeatureTogglesModule { + + @Provides + @Singleton + fun provideWalletFeatureToggles(featureTogglesManager: FeatureTogglesManager): WalletFeatureToggles { + return WalletFeatureToggles(featureTogglesManager) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggle/WalletFeatureToggles.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggle/WalletFeatureToggles.kt new file mode 100644 index 0000000000..e0881eead6 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggle/WalletFeatureToggles.kt @@ -0,0 +1,11 @@ +package com.tangem.feature.wallet.featuretoggle + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager + +internal class WalletFeatureToggles( + private val featureTogglesManager: FeatureTogglesManager, +) { + + val isTokenListLceFlowEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled("TOKEN_LIST_LCE_ENABLED") +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt index d4000aac56..068801b384 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt @@ -7,12 +7,14 @@ import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase +import com.tangem.domain.core.utils.getOrElse import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.ToggleTokenListGroupingUseCase import com.tangem.domain.tokens.ToggleTokenListSortingUseCase import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles import com.tangem.feature.wallet.presentation.organizetokens.analytics.PortfolioOrganizeTokensAnalyticsEvent import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState @@ -39,6 +41,7 @@ internal class OrganizeTokensViewModel @Inject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val analyticsEventsHandler: AnalyticsEventHandler, + private val walletFeatureToggles: WalletFeatureToggles, private val dispatchers: CoroutineDispatcherProvider, savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver, OrganizeTokensIntents { @@ -65,7 +68,7 @@ internal class OrganizeTokensViewModel @Inject constructor( UserWalletId(userWalletIdValue) } - private var tokenList: TokenList? = null + private var cachedTokenList: TokenList? = null val uiState: StateFlow = stateHolder.stateFlow @@ -89,7 +92,7 @@ internal class OrganizeTokensViewModel @Inject constructor( } override fun onSortClick() { - val list = tokenList ?: return + val list = cachedTokenList ?: return if (list.sortedBy == TokenList.SortType.BALANCE) return analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.ByBalance) @@ -99,14 +102,14 @@ internal class OrganizeTokensViewModel @Inject constructor( ifLeft = stateHolder::updateStateWithError, ifRight = { stateHolder.updateStateAfterTokenListSorting(it) - tokenList = it + cachedTokenList = it }, ) } } override fun onGroupClick() { - val list = tokenList ?: return + val list = cachedTokenList ?: return analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.Group) @@ -115,7 +118,7 @@ internal class OrganizeTokensViewModel @Inject constructor( ifLeft = stateHolder::updateStateWithError, ifRight = { stateHolder.updateStateAfterTokenListSorting(it) - tokenList = it + cachedTokenList = it }, ) } @@ -138,7 +141,7 @@ internal class OrganizeTokensViewModel @Inject constructor( val result = applyTokenListSortingUseCase( userWalletId = userWalletId, - sortedTokensIds = resolver.resolve(listState, tokenList), + sortedTokensIds = resolver.resolve(listState, cachedTokenList), isGroupedByNetwork = isGroupedByNetwork, isSortedByBalance = isSortedByBalance, ) @@ -161,16 +164,41 @@ internal class OrganizeTokensViewModel @Inject constructor( private fun bootstrapTokenList() { viewModelScope.launch(dispatchers.default) { - val maybeTokenList = getTokenListUseCase.launch(userWalletId) - .first { it.getOrNull()?.totalFiatBalance !is TokenList.FiatBalance.Loading } + val tokenList = getTokenList() ?: return@launch - maybeTokenList.fold( - ifLeft = stateHolder::updateStateWithError, - ifRight = { - stateHolder.updateStateWithTokenList(it) - tokenList = it - }, - ) + stateHolder.updateStateWithTokenList(tokenList) + cachedTokenList = tokenList + } + } + + private suspend fun getTokenList(): TokenList? { + return if (walletFeatureToggles.isTokenListLceFlowEnabled) { + val tokenList = getTokenListUseCase.launchLce(userWalletId) + .transform { maybeTokenList -> + val tokenList = maybeTokenList.getOrElse( + ifLoading = { return@transform }, + ifError = { error -> + stateHolder.updateStateWithError(error) + + return@transform + }, + ) + + emit(tokenList) + } + + tokenList.firstOrNull() + } else { + val maybeTokenList = getTokenListUseCase.launch(userWalletId) + .first { maybeTokenList -> + maybeTokenList.getOrNull()?.totalFiatBalance !is TokenList.FiatBalance.Loading + } + + maybeTokenList.getOrElse { error -> + stateHolder.updateStateWithError(error) + + null + } } } @@ -189,7 +217,7 @@ internal class OrganizeTokensViewModel @Inject constructor( if (dragOperationType !is DragAndDropAdapter.DragOperation.Type.End) return if (uiState.value.header.isSortedByBalance && dragOperationType.isItemsOrderChanged) { - tokenList = tokenList?.disableSortingByBalance() + cachedTokenList = cachedTokenList?.disableSortingByBalance() stateHolder.disableSortingByBalance() } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index 6185f157c6..df92e03ecc 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory @@ -26,6 +27,7 @@ internal class MultiWalletContentLoader( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, + private val walletFeatureToggles: WalletFeatureToggles, ) : WalletContentLoader(id = userWallet.walletId) { override fun create(): List { @@ -38,6 +40,7 @@ internal class MultiWalletContentLoader( walletWithFundsChecker = walletWithFundsChecker, getTokenListUseCase = getTokenListUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + walletFeatureToggles = walletFeatureToggles, applyTokenListSortingUseCase = applyTokenListSortingUseCase, ), MultiWalletWarningsSubscriber( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index 45ee41306b..cebb20d25f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory @@ -24,6 +25,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, + private val walletFeatureToggles: WalletFeatureToggles, ) { fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader { @@ -38,6 +40,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( getMultiWalletWarningsFactory = getMultiWalletWarningsFactory, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, applyTokenListSortingUseCase = applyTokenListSortingUseCase, + walletFeatureToggles = walletFeatureToggles, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index 0dcd95aa6a..d4bc6a472f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -1,9 +1,11 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers -import arrow.core.Either import arrow.core.getOrElse import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.core.utils.getOrElse import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet @@ -23,8 +25,6 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import timber.log.Timber -internal typealias MaybeTokenListFlow = Flow> - @Suppress("LongParameterList") internal abstract class BasicTokenListSubscriber( private val userWallet: UserWallet, @@ -38,7 +38,7 @@ internal abstract class BasicTokenListSubscriber( private val sendAnalyticsJobHolder = JobHolder() private val onTokenListReceivedJobHolder = JobHolder() - protected abstract fun tokenListFlow(): MaybeTokenListFlow + protected abstract fun tokenListFlow(): LceFlow override fun create(coroutineScope: CoroutineScope): Flow<*> { return combine( @@ -56,11 +56,17 @@ internal abstract class BasicTokenListSubscriber( }, flow2 = getSelectedAppCurrencyUseCase().distinctUntilChanged(), transform = { maybeTokenList, maybeAppCurrency -> - val tokenList = maybeTokenList.getOrElse { e -> - Timber.e("Failed to load token list: $e") - SetTokenListErrorTransformer(userWallet.walletId, e) - return@combine - } + val tokenList = maybeTokenList.getOrElse( + ifLoading = { maybeContent -> + maybeContent ?: return@combine + }, + ifError = { e -> + Timber.e("Failed to load token list: $e") + SetTokenListErrorTransformer(userWallet.walletId, e) + return@combine + }, + ) + val appCurrency = maybeAppCurrency.getOrElse { e -> Timber.e("Failed to load app currency: $e") AppCurrency.Default @@ -72,17 +78,17 @@ internal abstract class BasicTokenListSubscriber( ) } - protected open suspend fun onTokenListReceived(maybeTokenList: Either) { + protected open suspend fun onTokenListReceived(maybeTokenList: Lce) { /* no-op */ } - private suspend fun sendTokenListAnalytics(maybeTokenList: Either) { + private suspend fun sendTokenListAnalytics(maybeTokenList: Lce) { val displayedState = stateHolder.getWalletStateIfSelected(userWallet.walletId) tokenListAnalyticsSender.send( displayedUiState = displayedState, userWallet = userWallet, - tokenList = maybeTokenList.getOrElse { return }, + tokenList = maybeTokenList.getOrNull() ?: return, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 53114d04ba..b4529f9513 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -1,24 +1,28 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers -import arrow.core.Either -import arrow.core.getOrElse import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.core.utils.toLce import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import kotlinx.coroutines.flow.map @Suppress("LongParameterList") internal class MultiWalletTokenListSubscriber( private val userWallet: UserWallet, private val getTokenListUseCase: GetTokenListUseCase, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, + private val walletFeatureToggles: WalletFeatureToggles, stateHolder: WalletStateController, clickIntents: WalletClickIntents, tokenListAnalyticsSender: TokenListAnalyticsSender, @@ -33,16 +37,20 @@ internal class MultiWalletTokenListSubscriber( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, ) { - override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase.launch(userWallet.walletId) - - override suspend fun onTokenListReceived(maybeTokenList: Either) { - // TODO disabled for 5.7.2 because of potential critical - // updateSortingIfNeeded(maybeTokenList) + override fun tokenListFlow(): LceFlow { + return if (walletFeatureToggles.isTokenListLceFlowEnabled) { + getTokenListUseCase.launchLce(userWallet.walletId) + } else { + getTokenListUseCase.launch(userWallet.walletId).map { it.toLce() } + } } - @Suppress("UnusedPrivateMember") - private suspend fun updateSortingIfNeeded(maybeTokenList: Either) { - val tokenList = maybeTokenList.getOrElse { return } + override suspend fun onTokenListReceived(maybeTokenList: Lce) { + updateSortingIfNeeded(maybeTokenList) + } + + private suspend fun updateSortingIfNeeded(maybeTokenList: Lce) { + val tokenList = maybeTokenList.getOrNull() ?: return if (!checkNeedSorting(tokenList)) return applyTokenListSortingUseCase( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index 54c8ca07de..1431aff860 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -1,12 +1,17 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.core.utils.toLce import com.tangem.domain.tokens.GetCardTokensListUseCase +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import kotlinx.coroutines.flow.map @Suppress("LongParameterList") internal class SingleWalletWithTokenListSubscriber( @@ -26,5 +31,6 @@ internal class SingleWalletWithTokenListSubscriber( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, ) { - override fun tokenListFlow(): MaybeTokenListFlow = getCardTokensListUseCase(userWallet.walletId) + override fun tokenListFlow(): LceFlow = getCardTokensListUseCase(userWallet.walletId) + .map { it.toLce() } } \ No newline at end of file From cf59159d558fd5c6ba4dd01e8e411f87c41c3085 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2024 12:37:49 +0300 Subject: [PATCH 27/98] Updated on 2026-08-14 --- core/decompose/.gitignore | 1 + core/decompose/build.gradle.kts | 15 ++++ .../decompose/context/AppComponentContext.kt | 23 ++++++ .../AppComponentContextChildrenFactory.kt | 73 +++++++++++++++++++ .../context/DefaultAppComponentContext.kt | 35 +++++++++ .../core/decompose/di/ComponentScoped.kt | 9 +++ .../core/decompose/di/DecomposeComponent.kt | 47 ++++++++++++ .../decompose/di/HiltComponentBuilderOwner.kt | 12 +++ .../com/tangem/core/decompose/model/Model.kt | 36 +++++++++ .../core/decompose/model/ModelEntryPoint.kt | 51 +++++++++++++ .../navigation/AppNavigationProvider.kt | 28 +++++++ .../DefaultAppNavigationProvider.kt | 13 ++++ .../decompose/navigation/DefaultRouter.kt | 32 ++++++++ .../decompose/navigation/NavigationOwner.kt | 17 +++++ .../tangem/core/decompose/navigation/Route.kt | 6 ++ .../core/decompose/navigation/Router.kt | 31 ++++++++ .../decompose/ui/DefaultUiMessageSender.kt | 10 +++ .../com/tangem/core/decompose/ui/UiMessage.kt | 9 +++ .../core/decompose/ui/UiMessageHandler.kt | 17 +++++ .../core/decompose/ui/UiMessageSender.kt | 17 +++++ .../core/decompose/ui/UiMessageSenderOwner.kt | 12 +++ .../utils/ComponentCoroutineScope.kt | 20 +++++ .../decompose/utils/ComponentScopeOwner.kt | 16 ++++ .../core/decompose/utils/DispatchersOwner.kt | 14 ++++ .../tangem/core/decompose/utils/TagsOwner.kt | 12 +++ core/ui/build.gradle.kts | 1 + .../tangem/core/ui/message/EventMessage.kt | 47 ++++++++++++ .../core/ui/message/EventMessageEffect.kt | 43 +++++++++++ .../core/ui/message/EventMessageHandler.kt | 27 +++++++ .../coroutines/CoroutineDispatcherProvider.kt | 3 + gradle/dependencies.toml | 3 + settings.gradle.kts | 1 + 32 files changed, 681 insertions(+) create mode 100644 core/decompose/.gitignore create mode 100644 core/decompose/build.gradle.kts create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/context/AppComponentContext.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/context/AppComponentContextChildrenFactory.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/context/DefaultAppComponentContext.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/di/ComponentScoped.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/di/HiltComponentBuilderOwner.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/model/Model.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/AppNavigationProvider.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultAppNavigationProvider.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/NavigationOwner.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/Route.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/Router.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/DefaultUiMessageSender.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessage.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessageHandler.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessageSender.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessageSenderOwner.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/ComponentCoroutineScope.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/ComponentScopeOwner.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/DispatchersOwner.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/TagsOwner.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/message/EventMessage.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/message/EventMessageEffect.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/message/EventMessageHandler.kt diff --git a/core/decompose/.gitignore b/core/decompose/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/core/decompose/.gitignore @@ -0,0 +1 @@ +/build diff --git a/core/decompose/build.gradle.kts b/core/decompose/build.gradle.kts new file mode 100644 index 0000000000..7dfba4d379 --- /dev/null +++ b/core/decompose/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + alias(deps.plugins.kotlin.jvm) + alias(deps.plugins.kotlin.kapt) + id("configuration") +} + +dependencies { + implementation(projects.core.utils) + + api(deps.decompose) + implementation(deps.kotlin.coroutines) + + implementation(deps.hilt.core) + kapt(deps.hilt.kapt) +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/AppComponentContext.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/AppComponentContext.kt new file mode 100644 index 0000000000..9426735800 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/AppComponentContext.kt @@ -0,0 +1,23 @@ +package com.tangem.core.decompose.context + +import com.arkivanov.decompose.ComponentContext +import com.tangem.core.decompose.di.HiltComponentBuilderOwner +import com.tangem.core.decompose.navigation.NavigationOwner +import com.tangem.core.decompose.ui.UiMessageSenderOwner +import com.tangem.core.decompose.utils.ComponentScopeOwner +import com.tangem.core.decompose.utils.DispatchersOwner +import com.tangem.core.decompose.utils.TagsOwner + +/** + * Interface for the application component context. + * + * It combines several other interfaces related to navigation, dispatching, UI messaging, etc. + */ +interface AppComponentContext : + ComponentContext, + NavigationOwner, + ComponentScopeOwner, + DispatchersOwner, + UiMessageSenderOwner, + HiltComponentBuilderOwner, + TagsOwner \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/AppComponentContextChildrenFactory.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/AppComponentContextChildrenFactory.kt new file mode 100644 index 0000000000..146a7e2bb0 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/AppComponentContextChildrenFactory.kt @@ -0,0 +1,73 @@ +package com.tangem.core.decompose.context + +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.childContext +import com.arkivanov.essenty.lifecycle.Lifecycle +import com.tangem.core.decompose.di.HiltComponentBuilderOwner +import com.tangem.core.decompose.navigation.NavigationOwner +import com.tangem.core.decompose.navigation.Router +import com.tangem.core.decompose.ui.DefaultUiMessageSender +import com.tangem.core.decompose.ui.UiMessageHandler +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.decompose.ui.UiMessageSenderOwner +import com.tangem.core.decompose.utils.ComponentCoroutineScope +import com.tangem.core.decompose.utils.DispatchersOwner +import kotlinx.coroutines.CoroutineScope + +/** + * Creates a new child [AppComponentContext] with the provided [key] and optional [lifecycle]. + * + * @param key The key to use. + * @param lifecycle The [Lifecycle] to use. If not provided, the parent's lifecycle will be used. + * @param router The [Router] to use in the child. If not provided, the parent's router will be used. + * @param messageHandler The [UiMessageHandler] to use in the child. If not provided, the parent's message sender will + * be used. + + * + * @see childByContext + * */ +fun AppComponentContext.child( + key: String, + lifecycle: Lifecycle? = null, + router: Router? = null, + messageHandler: UiMessageHandler? = null, +): AppComponentContext = childByContext( + componentContext = childContext(key, lifecycle), + router = router, + messageHandler = messageHandler, +) + +/** + * Creates a new child [AppComponentContext] with the provided [componentContext]. + * + * @param componentContext The [ComponentContext] to use. + * @param router The [Router] to use in the child. If not provided, the parent's router will be used. + * @param messageHandler The [UiMessageHandler] to use in the child. If not provided, the parent's message sender will + * be used. + + * + * @see child + * */ +fun AppComponentContext.childByContext( + componentContext: ComponentContext, + router: Router? = null, + messageHandler: UiMessageHandler? = null, +): AppComponentContext = object : + AppComponentContext, + ComponentContext by componentContext, + NavigationOwner by this@childByContext, + UiMessageSenderOwner by this@childByContext, + DispatchersOwner by this@childByContext, + HiltComponentBuilderOwner by this@childByContext { + + override val tags: HashMap = HashMap() + + override val componentScope: CoroutineScope = ComponentCoroutineScope(lifecycle, dispatchers) + + override val messageSender: UiMessageSender = messageHandler + ?.let(::DefaultUiMessageSender) + ?: this@childByContext.messageSender + + override val router: Router + get() = router ?: this@childByContext.router +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/DefaultAppComponentContext.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/DefaultAppComponentContext.kt new file mode 100644 index 0000000000..586779ec4a --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/DefaultAppComponentContext.kt @@ -0,0 +1,35 @@ +package com.tangem.core.decompose.context + +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.essenty.instancekeeper.getOrCreate +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.navigation.AppNavigationProvider +import com.tangem.core.decompose.navigation.DefaultAppNavigationProvider +import com.tangem.core.decompose.navigation.DefaultRouter +import com.tangem.core.decompose.navigation.Router +import com.tangem.core.decompose.ui.DefaultUiMessageSender +import com.tangem.core.decompose.ui.UiMessageHandler +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.decompose.utils.ComponentCoroutineScope +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.CoroutineScope + +class DefaultAppComponentContext( + componentContext: ComponentContext, + messageHandler: UiMessageHandler, + override val dispatchers: CoroutineDispatcherProvider, + override val hiltComponentBuilder: DecomposeComponent.Builder, +) : AppComponentContext, ComponentContext by componentContext { + + override val tags: HashMap = HashMap() + + override val componentScope: CoroutineScope = ComponentCoroutineScope(lifecycle, dispatchers) + + override val messageSender: UiMessageSender = DefaultUiMessageSender(messageHandler) + + override val navigationProvider: AppNavigationProvider + get() = instanceKeeper.getOrCreate { DefaultAppNavigationProvider() } + + override val router: Router + get() = instanceKeeper.getOrCreate { DefaultRouter(navigationProvider) } +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/ComponentScoped.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/ComponentScoped.kt new file mode 100644 index 0000000000..b5fde3b58f --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/ComponentScoped.kt @@ -0,0 +1,9 @@ +package com.tangem.core.decompose.di + +/** + * Annotation for marking a dependency as a component scoped. + * + * This means that the lifecycle of the dependency is limited to the lifecycle of the component it is attached to. + */ +@Retention(AnnotationRetention.SOURCE) +annotation class ComponentScoped \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt new file mode 100644 index 0000000000..b5df451866 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt @@ -0,0 +1,47 @@ +package com.tangem.core.decompose.di + +import com.tangem.core.decompose.navigation.Router +import com.tangem.core.decompose.ui.UiMessageSender +import dagger.BindsInstance +import dagger.hilt.DefineComponent +import dagger.hilt.components.SingletonComponent + +/** + * Interface for the Decompose component. + * + * It is annotated as [ComponentScoped], meaning it has a lifecycle that is scoped to the component. + */ +@ComponentScoped +@DefineComponent(parent = SingletonComponent::class) +interface DecomposeComponent { + + /** + * Builder interface for the component. + */ + @DefineComponent.Builder + interface Builder { + + /** + * Sets the router for the component. + * + * @param router The router to set. + * @return The builder instance. + */ + fun router(@BindsInstance router: Router): Builder + + /** + * Sets the UI message sender for the component. + * + * @param uiMessageSender The UI message sender to set. + * @return The builder instance. + */ + fun uiMessageSender(@BindsInstance uiMessageSender: UiMessageSender): Builder + + /** + * Builds the Decompose component. + * + * @return The built Decompose component. + */ + fun build(): DecomposeComponent + } +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/HiltComponentBuilderOwner.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/HiltComponentBuilderOwner.kt new file mode 100644 index 0000000000..d210c58019 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/HiltComponentBuilderOwner.kt @@ -0,0 +1,12 @@ +package com.tangem.core.decompose.di + +/** + * Interface for owning a Hilt component builder. + */ +interface HiltComponentBuilderOwner { + + /** + * Provides access to the Hilt component builder instance. + */ + val hiltComponentBuilder: DecomposeComponent.Builder +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/Model.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/Model.kt new file mode 100644 index 0000000000..b7a6a28adb --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/Model.kt @@ -0,0 +1,36 @@ +package com.tangem.core.decompose.model + +import com.arkivanov.essenty.instancekeeper.InstanceKeeper +import com.tangem.core.decompose.navigation.Router +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel + +/** + * Abstract class for a component's model. + * + * It provides access to the coroutine dispatchers and a coroutine scope which will survive re-creation of component + * and will be destroyed when the component is destroyed. + * + * Also, it can inject and use some component features like [Router] and [UiMessageSender]. + */ +abstract class Model : InstanceKeeper.Instance { + + /** + * Provides access to the coroutine dispatchers. + */ + protected abstract val dispatchers: CoroutineDispatcherProvider + + /** + * The coroutine scope for the model. That will be cancelled when the model is destroyed. + */ + protected val modelScope by lazy { + CoroutineScope(context = dispatchers.mainImmediate + SupervisorJob()) + } + + override fun onDestroy() { + runCatching { modelScope.cancel() } + } +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt new file mode 100644 index 0000000000..58cad4cf7a --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt @@ -0,0 +1,51 @@ +package com.tangem.core.decompose.model + +import com.arkivanov.essenty.instancekeeper.getOrCreate +import com.arkivanov.essenty.instancekeeper.getOrCreateSimple +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.di.DecomposeComponent +import dagger.hilt.EntryPoint +import dagger.hilt.EntryPoints +import dagger.hilt.InstallIn +import javax.inject.Provider + +/** + * Entry point for the models in the application. + * + * It provides a map of model providers. + */ +@EntryPoint +@InstallIn(DecomposeComponent::class) +interface ModelsEntryPoint { + + fun models(): Map, Provider> +} + +/** + * Gets or creates a component's [Model]. + */ +inline fun AppComponentContext.getOrCreateModel(): M { + val modelKey = "model_${M::class.simpleName}" + + val entryPoint = instanceKeeper.getOrCreateSimple(key = "modelsEntryPoint") { + val hiltComponent = hiltComponentBuilder + .router(router) + .uiMessageSender(messageSender) + .build() + + EntryPoints.get(hiltComponent, ModelsEntryPoint::class.java) + } + + val model = instanceKeeper.getOrCreate(modelKey) { + requireNotNull(entryPoint.models()[M::class.java]?.get()) { + "Model ${M::class.simpleName} is not provided" + } + } + + val isModelExist = tags.getOrElse(modelKey) { false } as Boolean + if (!isModelExist) { + tags[modelKey] = true + } + + return model as M +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/AppNavigationProvider.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/AppNavigationProvider.kt new file mode 100644 index 0000000000..a269e5622f --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/AppNavigationProvider.kt @@ -0,0 +1,28 @@ +@file:Suppress("UNCHECKED_CAST") + +package com.tangem.core.decompose.navigation + +import com.arkivanov.decompose.router.stack.StackNavigation + +/** + * Interface for providing application navigation. + * It provides or creates a StackNavigation instance for the application. + */ +interface AppNavigationProvider { + + /** + * Gets or creates a StackNavigation instance. + * + * @return The StackNavigation instance. + */ + fun getOrCreate(): StackNavigation +} + +/** + * Gets or creates a [StackNavigation] instance of a specific type. + * + * @return The [StackNavigation] instance. + */ +fun AppNavigationProvider.getOrCreateTyped(): StackNavigation { + return getOrCreate() as StackNavigation +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultAppNavigationProvider.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultAppNavigationProvider.kt new file mode 100644 index 0000000000..76a8089d75 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultAppNavigationProvider.kt @@ -0,0 +1,13 @@ +package com.tangem.core.decompose.navigation + +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.essenty.instancekeeper.InstanceKeeper + +internal class DefaultAppNavigationProvider : AppNavigationProvider, InstanceKeeper.Instance { + + private var navigation: StackNavigation? = null + + override fun getOrCreate(): StackNavigation { + return navigation ?: StackNavigation().also { navigation = it } + } +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt new file mode 100644 index 0000000000..50e3840442 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt @@ -0,0 +1,32 @@ +package com.tangem.core.decompose.navigation + +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.pop +import com.arkivanov.decompose.router.stack.popWhile +import com.arkivanov.decompose.router.stack.pushNew +import com.arkivanov.essenty.instancekeeper.InstanceKeeper + +internal class DefaultRouter( + private val navigationProvider: AppNavigationProvider, +) : Router, InstanceKeeper.Instance { + + private val navigation: StackNavigation + get() = navigationProvider.getOrCreate() + + @OptIn(ExperimentalDecomposeApi::class) + override fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit) { + navigation.pushNew(route, onComplete) + } + + override fun pop(onComplete: (isSuccess: Boolean) -> Unit) { + navigation.pop(onComplete) + } + + override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) { + navigation.popWhile( + predicate = { it != route }, + onComplete = onComplete, + ) + } +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/NavigationOwner.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/NavigationOwner.kt new file mode 100644 index 0000000000..d1f9c0bfa3 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/NavigationOwner.kt @@ -0,0 +1,17 @@ +package com.tangem.core.decompose.navigation + +/** + * Interface for owning navigation-related properties. + */ +interface NavigationOwner { + + /** + * The [Router] instance. + */ + val router: Router + + /** + * The [AppNavigationProvider] instance. + */ + val navigationProvider: AppNavigationProvider +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/Route.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/Route.kt new file mode 100644 index 0000000000..08d84f1a56 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/Route.kt @@ -0,0 +1,6 @@ +package com.tangem.core.decompose.navigation + +/** + * Interface for a route in the application. + */ +interface Route \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/Router.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/Router.kt new file mode 100644 index 0000000000..3a9b7d93ca --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/Router.kt @@ -0,0 +1,31 @@ +package com.tangem.core.decompose.navigation + +/** + * Interface for a router in the application. + * It provides methods for navigating through the application. + */ +interface Router { + + /** + * Pushes a new route to the navigation stack. + * + * @param route The route to push. + * @param onComplete The callback to be invoked when the operation is complete. + */ + fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit = {}) + + /** + * Pops the top route from the navigation stack. + * + * @param onComplete The callback to be invoked when the operation is complete. + */ + fun pop(onComplete: (isSuccess: Boolean) -> Unit = {}) + + /** + * Pops routes from the navigation stack until the specified route is found. + * + * @param route The route to pop to. + * @param onComplete The callback to be invoked when the operation is complete. + */ + fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit = {}) +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/DefaultUiMessageSender.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/DefaultUiMessageSender.kt new file mode 100644 index 0000000000..59546d42e0 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/DefaultUiMessageSender.kt @@ -0,0 +1,10 @@ +package com.tangem.core.decompose.ui + +internal class DefaultUiMessageSender( + private val handler: UiMessageHandler, +) : UiMessageSender { + + override fun send(message: UiMessage) { + handler.handleMessage(message) + } +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessage.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessage.kt new file mode 100644 index 0000000000..70be07d41c --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessage.kt @@ -0,0 +1,9 @@ +package com.tangem.core.decompose.ui + +/** + * Interface for a message that can be sent to a [UiMessageSender] and handled by a [UiMessageHandler]. + * + * @see UiMessageSender + * @see UiMessageHandler + */ +interface UiMessage \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessageHandler.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessageHandler.kt new file mode 100644 index 0000000000..8633b81eb9 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessageHandler.kt @@ -0,0 +1,17 @@ +package com.tangem.core.decompose.ui + +/** + * Interface for handling UI messages. + * + * @see UiMessage + * @see UiMessageSender + */ +interface UiMessageHandler { + + /** + * Handles the given UI message. + * + * @param message The UI message to handle. + */ + fun handleMessage(message: UiMessage) +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessageSender.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessageSender.kt new file mode 100644 index 0000000000..9b58bbb09c --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessageSender.kt @@ -0,0 +1,17 @@ +package com.tangem.core.decompose.ui + +/** + * Interface for sending messages to UI. + * + * @see UiMessage + * @see UiMessageHandler + * */ +interface UiMessageSender { + + /** + * Sends the given UI message. + * + * @param message The UI message to send. + * */ + fun send(message: UiMessage) +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessageSenderOwner.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessageSenderOwner.kt new file mode 100644 index 0000000000..f53e420e9c --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/UiMessageSenderOwner.kt @@ -0,0 +1,12 @@ +package com.tangem.core.decompose.ui + +/** + * Interface for owning a [UiMessageSender]. + * */ +interface UiMessageSenderOwner { + + /** + * The [UiMessageSender] instance. + * */ + val messageSender: UiMessageSender +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/ComponentCoroutineScope.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/ComponentCoroutineScope.kt new file mode 100644 index 0000000000..dcfcb4aa7e --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/ComponentCoroutineScope.kt @@ -0,0 +1,20 @@ +package com.tangem.core.decompose.utils + +import com.arkivanov.essenty.lifecycle.Lifecycle +import com.arkivanov.essenty.lifecycle.doOnDestroy +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel + +/** + + * [CoroutineDispatcherProvider.mainImmediate] dispatcher. + * */ +@Suppress("FunctionName") +internal fun ComponentCoroutineScope(lifecycle: Lifecycle, dispatchers: CoroutineDispatcherProvider): CoroutineScope { + val scope = CoroutineScope(context = dispatchers.mainImmediate + SupervisorJob()) + lifecycle.doOnDestroy(scope::cancel) + + return scope +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/ComponentScopeOwner.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/ComponentScopeOwner.kt new file mode 100644 index 0000000000..75152213c6 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/ComponentScopeOwner.kt @@ -0,0 +1,16 @@ +package com.tangem.core.decompose.utils + +import kotlinx.coroutines.CoroutineScope + +/** + * Interface for owning a component scope. + */ +interface ComponentScopeOwner { + + /** + * Provides access to the component's [CoroutineScope] instance. + * + * This scope is used for launching coroutines that are bound to the component's lifecycle. + */ + val componentScope: CoroutineScope +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/DispatchersOwner.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/DispatchersOwner.kt new file mode 100644 index 0000000000..94163474ff --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/DispatchersOwner.kt @@ -0,0 +1,14 @@ +package com.tangem.core.decompose.utils + +import com.tangem.utils.coroutines.CoroutineDispatcherProvider + +/** + * Interface for owning a [CoroutineDispatcherProvider]. + */ +interface DispatchersOwner { + + /** + * Provides access to the [CoroutineDispatcherProvider] instance. + */ + val dispatchers: CoroutineDispatcherProvider +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/TagsOwner.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/TagsOwner.kt new file mode 100644 index 0000000000..d7cd00ac80 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/utils/TagsOwner.kt @@ -0,0 +1,12 @@ +package com.tangem.core.decompose.utils + +/** + * Interface for owning tags. + */ +interface TagsOwner { + + /** + * Provides access to the tags map instance. + */ + val tags: HashMap +} \ No newline at end of file diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index e2abfa0f4b..961dc949c5 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { /** Project - Core */ implementation(projects.core.res) implementation(projects.core.utils) + implementation(projects.core.decompose) /** AndroidX libraries */ implementation(deps.androidx.fragment.ktx) diff --git a/core/ui/src/main/java/com/tangem/core/ui/message/EventMessage.kt b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessage.kt new file mode 100644 index 0000000000..c43611f1c4 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessage.kt @@ -0,0 +1,47 @@ +package com.tangem.core.ui.message + +import androidx.compose.material.SnackbarDuration +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import com.tangem.core.decompose.ui.UiMessage +import com.tangem.core.ui.extensions.TextReference + +/** + * Event that is used to show a message in the UI. + * + * @see EventMessageHandler + * */ +@Immutable +sealed interface EventMessage : UiMessage + +/** + * Shows a snackbar. + * + * @param message The message to show. + * @param duration The duration of the snackbar. + * @param actionLabel The label of the action button. + * @param action The action to perform when the action button is clicked. + * */ +data class SnackbarMessage( + val message: TextReference, + val duration: SnackbarDuration = SnackbarDuration.Short, + val actionLabel: TextReference? = null, + val action: (() -> Unit)? = null, +) : EventMessage + +/** + * Shows a [content] in the UI. + * + * @param content The content to show. + * */ +data class ContentMessage(val content: Content) : EventMessage { + + @Stable + fun interface Content { + + @Suppress("ComposableFunctionName", "TopLevelComposableFunctions") + @Composable + operator fun invoke(onDismiss: () -> Unit) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageEffect.kt b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageEffect.kt new file mode 100644 index 0000000000..1cff53accd --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageEffect.kt @@ -0,0 +1,43 @@ +package com.tangem.core.ui.message + +import android.content.Context +import androidx.compose.material.SnackbarHostState +import androidx.compose.material.SnackbarResult +import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalContext +import com.tangem.core.ui.event.EventEffect +import com.tangem.core.ui.extensions.resolveReference + +@Composable +fun EventMessageEffect(messageHandler: EventMessageHandler, snackbarHostState: SnackbarHostState) { + val messageEvent by messageHandler.collectAsState() + val context = LocalContext.current + var contentMessage: ContentMessage? by remember { mutableStateOf(value = null) } + + EventEffect(event = messageEvent) { message -> + when (message) { + is ContentMessage -> { + contentMessage = message + } + is SnackbarMessage -> { + showSnackbar(snackbarHostState, message, context) + } + } + } + + contentMessage?.content?.invoke( + onDismiss = { contentMessage = null }, + ) +} + +private suspend fun showSnackbar(snackbarHostState: SnackbarHostState, message: SnackbarMessage, context: Context) { + val result = snackbarHostState.showSnackbar( + message = message.message.resolveReference(context.resources), + actionLabel = message.actionLabel?.resolveReference(context.resources), + duration = message.duration, + ) + + if (result == SnackbarResult.ActionPerformed) { + message.action?.invoke() + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageHandler.kt b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageHandler.kt new file mode 100644 index 0000000000..a8bc0cdfae --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageHandler.kt @@ -0,0 +1,27 @@ +package com.tangem.core.ui.message + +import com.tangem.core.decompose.ui.UiMessage +import com.tangem.core.decompose.ui.UiMessageHandler +import com.tangem.core.ui.event.StateEvent +import com.tangem.core.ui.event.consumedEvent +import com.tangem.core.ui.event.triggeredEvent +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +/** + * Message handler that is used to show or remove an [EventMessage] in the UI. + */ +class EventMessageHandler( + private val events: MutableStateFlow>, +) : UiMessageHandler, StateFlow> by events { + + override fun handleMessage(message: UiMessage) { + if (message !is EventMessage) return + + events.value = triggeredEvent(message, ::consumeEvent) + } + + private fun consumeEvent() { + events.value = consumedEvent() + } +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt index f15bce01ea..53cfb7ed29 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt @@ -8,6 +8,7 @@ import javax.inject.Inject interface CoroutineDispatcherProvider { val main: CoroutineDispatcher + val mainImmediate: CoroutineDispatcher val io: CoroutineDispatcher val default: CoroutineDispatcher val single: CoroutineDispatcher @@ -15,6 +16,7 @@ interface CoroutineDispatcherProvider { class AppCoroutineDispatcherProvider @Inject constructor() : CoroutineDispatcherProvider { override val main: CoroutineDispatcher = Dispatchers.Main + override val mainImmediate: CoroutineDispatcher = Dispatchers.Main.immediate override val io: CoroutineDispatcher = Dispatchers.IO override val default: CoroutineDispatcher = Dispatchers.Default override val single: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() @@ -22,6 +24,7 @@ class AppCoroutineDispatcherProvider @Inject constructor() : CoroutineDispatcher class TestingCoroutineDispatcherProvider( override val main: CoroutineDispatcher = Dispatchers.Unconfined, + override val mainImmediate: CoroutineDispatcher = Dispatchers.Unconfined, override val io: CoroutineDispatcher = Dispatchers.Unconfined, override val default: CoroutineDispatcher = Dispatchers.Unconfined, override val single: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher(), diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 4f36bd773a..9ce9f25864 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -82,6 +82,7 @@ swipeRefreshLayout = "1.1.0" spr-client = "3.6.2" web3j = "4.10.1" leakcanary = "2.13" +decompose = "2.2.2" # endregion Other libraries # region Tangem @@ -252,4 +253,6 @@ camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = camera-view = { module = "androidx.camera:camera-view", version.ref = "androidXCamera" } web3j-core = { module = "org.web3j:core", version.ref = "web3j" } leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" } +decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" } +decompose-ext-compose = { module = "com.arkivanov.decompose:extensions-compose-jetpack", version.ref = "decompose" } # endregion Other diff --git a/settings.gradle.kts b/settings.gradle.kts index f276efc474..1a729a943f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -72,6 +72,7 @@ include(":core:ui") include(":core:utils") include(":core:deep-links") include(":core:deep-links:global") +include(":core:decompose") // endregion Core modules // region Libs modules From 0450c67d5945cfb6acf6d5128c8a8cfcef93edb4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 14:09:11 +0100 Subject: [PATCH 28/98] Updated on 2026-08-14 --- .../redux/OnboardingWalletMiddleware.kt | 27 +++++++++++-------- gradle/dependencies.toml | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index cfcd3f41af..f4248022b7 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -11,7 +11,6 @@ import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction -import com.tangem.domain.common.TapWorkarounds.canSkipBackup import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO @@ -418,15 +417,11 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) when (val error = result.error) { is TangemSdkError.BackupFailedNotEmptyWallets -> { - if (card?.canSkipBackup == false) { - store.dispatchOnMain( - GlobalAction.ShowDialog( - BackupDialog.ResetBackupCard(error.cardId), - ), - ) - } else { - crashlytics.recordException(error) - } + store.dispatchOnMain( + GlobalAction.ShowDialog( + BackupDialog.ResetBackupCard(error.cardId), + ), + ) } is TangemSdkError.IssuerSignatureLoadingFailed -> { store.dispatchOnMain( @@ -497,7 +492,17 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) store.dispatchOnMain(BackupAction.PrepareToWriteBackupCard(action.cardNumber + 1)) } } - is CompletionResult.Failure -> Unit + is CompletionResult.Failure -> { + when (val error = result.error) { + is TangemSdkError.BackupFailedNotEmptyWallets -> { + store.dispatchOnMain( + GlobalAction.ShowDialog( + BackupDialog.ResetBackupCard(error.cardId), + ), + ) + } + } + } } } } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index c58b88a4a5..c246ab9f05 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ decompose = "2.2.2" # region Tangem tangemBlockchainSdk = "develop-601" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-345" +tangemCardSdk = "develop-346" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem From 8b566c31ae2d34a2a3a2be29e1504708d4e585d4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 19 Apr 2024 15:20:47 +0200 Subject: [PATCH 29/98] Updated on 2026-08-14 --- .../tangem/tap/domain/sdk/TangemSdkManager.kt | 30 ++++ .../sdk/impl/DefaultTangemSdkManager.kt | 50 +++++++ .../domain/sdk/impl/MockTangemSdkManager.kt | 90 +++++++----- .../tangem/tap/domain/sdk/mocks/MockConfig.kt | 8 ++ .../tap/domain/sdk/mocks/MockProvider.kt | 43 +++++- .../com/tangem/tap/domain/sdk/mocks/Mocks.kt | 24 ++++ .../domain/sdk/mocks/wallet/WalletMocks.kt | 130 +++++++++++++++++- .../domain/sdk/mocks/wallet2/Wallet2Mocks.kt | 33 +++++ .../tap/domain/twins/TwinCardsManager.kt | 13 +- app/src/mocked/res/values/strings.xml | 6 + .../res/xml/network_security_config.xml | 9 ++ 11 files changed, 391 insertions(+), 45 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockConfig.kt create mode 100644 app/src/mocked/res/values/strings.xml create mode 100644 app/src/mocked/res/xml/network_security_config.xml diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt index 407643fb6d..7a576f77b7 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt @@ -13,8 +13,10 @@ import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse +@Suppress("TooManyFunctions") interface TangemSdkManager { val canUseBiometry: Boolean @@ -38,6 +40,7 @@ interface TangemSdkManager { shouldReset: Boolean = false, ): CompletionResult + // Wallet2 specific suspend fun importWallet( scanResponse: ScanResponse, mnemonic: String, @@ -80,6 +83,10 @@ interface TangemSdkManager { allowRequestAccessCodeFromRepository: Boolean = false, ): CompletionResult + @Deprecated( + "TangemSdkManager shouldn't run custom tasks. " + + "All of them should be specified in TangemSdkManager certain methods.", + ) suspend fun runTaskAsync( runnable: CardSessionRunnable, cardId: String? = null, @@ -95,4 +102,27 @@ interface TangemSdkManager { fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) + + // region Twin-specific + + suspend fun finalizeTwin( + secondCardPublicKey: ByteArray, + issuerKeyPair: KeyPair, + cardId: String, + initialMessage: Message, + ): CompletionResult + + suspend fun createFirstTwinWallet(cardId: String, initialMessage: Message): CompletionResult + + @Suppress("LongParameterList") + suspend fun createSecondTwinWallet( + firstPublicKey: String, + firstCardId: String, + issuerKeys: KeyPair, + preparingMessage: Message, + creatingWalletMessage: Message, + initialMessage: Message, + ): CompletionResult + + // endregion } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index b6d9589111..3955158fd9 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -28,12 +28,16 @@ import com.tangem.operations.pins.SetUserCodeCommand import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.operations.derivation.DeriveWalletPublicKeyTask +import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.tap.derivationsFinder import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.tasks.product.CreateProductWalletTask import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask +import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask +import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask +import com.tangem.tap.domain.twins.FinalizeTwinTask import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.suspendCancellableCoroutine @@ -274,6 +278,52 @@ class DefaultTangemSdkManager( tangemSdk.config.userCodeRequestPolicy = policy } + // region Twin-specific + + override suspend fun createFirstTwinWallet( + cardId: String, + initialMessage: Message, + ): CompletionResult { + return runTaskAsync( + runnable = CreateFirstTwinWalletTask(cardId), + cardId = cardId, + initialMessage = initialMessage, + ) + } + + override suspend fun createSecondTwinWallet( + firstPublicKey: String, + firstCardId: String, + issuerKeys: KeyPair, + preparingMessage: Message, + creatingWalletMessage: Message, + initialMessage: Message, + ): CompletionResult { + val task = CreateSecondTwinWalletTask( + firstPublicKey = firstPublicKey, + firstCardId = firstCardId, + issuerKeys = issuerKeys, + preparingMessage = preparingMessage, + creatingWalletMessage = creatingWalletMessage, + ) + return runTaskAsync(task, null, initialMessage) + } + + override suspend fun finalizeTwin( + secondCardPublicKey: ByteArray, + issuerKeyPair: KeyPair, + cardId: String, + initialMessage: Message, + ): CompletionResult { + return runTaskAsync( + runnable = FinalizeTwinTask(secondCardPublicKey, issuerKeyPair), + cardId = cardId, + initialMessage = initialMessage, + ) + } + + // endregion + companion object { @Deprecated("Use [DefaultCardSdkProvider] instead") val config = Config( diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt index 8279c72670..e690748bd5 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt @@ -5,54 +5,51 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import com.tangem.Message import com.tangem.common.* -import com.tangem.common.authentication.keystore.KeystoreManager +import com.tangem.common.authentication.keystore.DummyKeystoreManager import com.tangem.common.core.* import com.tangem.common.extensions.ByteArrayKey -import com.tangem.common.services.secure.SecureStorage +import com.tangem.common.services.InMemoryStorage import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.sdk.mocks.MockProvider import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext @Suppress("TooManyFunctions") class MockTangemSdkManager( private val resources: Resources, ) : TangemSdkManager { - override val canUseBiometry: Boolean - get() = false + override val canUseBiometry = false - override val needEnrollBiometrics: Boolean - get() = TODO() + override val needEnrollBiometrics = false - override val keystoreManager: KeystoreManager - get() = TODO() + override val keystoreManager = DummyKeystoreManager() - override val secureStorage: SecureStorage - get() = TODO() + override val secureStorage = InMemoryStorage() override val userCodeRequestPolicy: UserCodeRequestPolicy - get() = TODO() + get() = userCodeRequestPolicyInternal + + private var userCodeRequestPolicyInternal: UserCodeRequestPolicy = UserCodeRequestPolicy.Default override suspend fun scanProduct( cardId: String?, messageRes: Int?, allowsRequestAccessCodeFromRepository: Boolean, ): CompletionResult { - return CompletionResult.Success(MockProvider.getScanResponse()) + return MockProvider.getScanResponse() } override suspend fun createProductWallet( scanResponse: ScanResponse, shouldReset: Boolean, ): CompletionResult { - TODO() + return MockProvider.getCreateProductWalletResponse() } override suspend fun importWallet( @@ -61,14 +58,14 @@ class MockTangemSdkManager( passphrase: String?, shouldReset: Boolean, ): CompletionResult { - TODO() + return MockProvider.getImportWalletResponse() } override suspend fun derivePublicKeys( cardId: String?, derivations: Map>, ): CompletionResult { - TODO() + return MockProvider.getDerivationTaskResponse() } override suspend fun deriveExtendedPublicKey( @@ -76,52 +73,52 @@ class MockTangemSdkManager( walletPublicKey: ByteArray, derivation: DerivationPath, ): CompletionResult { - TODO() + return MockProvider.getExtendedPublicKey() } override suspend fun resetToFactorySettings( cardId: String, allowsRequestAccessCodeFromRepository: Boolean, ): CompletionResult { - TODO() + return MockProvider.getCardDto() } override suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult { - TODO() + return CompletionResult.Success(Unit) } override suspend fun deleteSavedUserCodes(cardsIds: Set): CompletionResult { - TODO() + return CompletionResult.Success(Unit) } override suspend fun clearSavedUserCodes(): CompletionResult { - TODO() + return CompletionResult.Success(Unit) } override suspend fun setPasscode(cardId: String?): CompletionResult { - TODO() + return MockProvider.getSuccessResponse() } override suspend fun setAccessCode(cardId: String?): CompletionResult { - TODO() + return MockProvider.getSuccessResponse() } override suspend fun setLongTap(cardId: String?): CompletionResult { - TODO() + return MockProvider.getSuccessResponse() } override suspend fun setAccessCodeRecoveryEnabled( cardId: String?, enabled: Boolean, ): CompletionResult { - TODO() + return MockProvider.getSuccessResponse() } override suspend fun scanCard( cardId: String?, allowRequestAccessCodeFromRepository: Boolean, ): CompletionResult { - TODO() + return MockProvider.getCardDto() } override suspend fun runTaskAsync( @@ -130,12 +127,10 @@ class MockTangemSdkManager( initialMessage: Message?, accessCode: String?, @DrawableRes iconScanRes: Int?, - ): CompletionResult = withContext(Dispatchers.Main) { - TODO() - } + ): CompletionResult = error("This method is deprecated") - @Suppress("MagicNumber") override fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) { + // intentionally do nothing } @Deprecated("TangemSdkManager shouldn't returns a string from resources") @@ -144,6 +139,37 @@ class MockTangemSdkManager( } override fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) { - TODO() + userCodeRequestPolicyInternal = policy } + + // region Twin-specific + + override suspend fun createFirstTwinWallet( + cardId: String, + initialMessage: Message, + ): CompletionResult { + return MockProvider.createFirstTwinWallet() + } + + override suspend fun createSecondTwinWallet( + firstPublicKey: String, + firstCardId: String, + issuerKeys: KeyPair, + preparingMessage: Message, + creatingWalletMessage: Message, + initialMessage: Message, + ): CompletionResult { + return MockProvider.createSecondTwinWallet() + } + + override suspend fun finalizeTwin( + secondCardPublicKey: ByteArray, + issuerKeyPair: KeyPair, + cardId: String, + initialMessage: Message, + ): CompletionResult { + return MockProvider.finalizeTwin() + } + + // endregion } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockConfig.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockConfig.kt new file mode 100644 index 0000000000..4ae2e1429d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockConfig.kt @@ -0,0 +1,8 @@ +package com.tangem.tap.domain.sdk.mocks + +import arrow.core.Either +import com.tangem.domain.models.scan.ProductType + +data class MockConfig( + val content: Either, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt index 0d0c1eabed..b4658912c7 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt @@ -1,14 +1,43 @@ package com.tangem.tap.domain.sdk.mocks +import com.tangem.common.CompletionResult import com.tangem.domain.models.scan.ProductType import com.tangem.tap.domain.sdk.mocks.wallet.WalletMocks import com.tangem.tap.domain.sdk.mocks.wallet2.Wallet2Mocks +import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse object MockProvider { - var productType: ProductType = ProductType.Wallet + private var mocks: Mocks = getMocks(ProductType.Wallet) - fun getScanResponse() = getMocks(productType).scanResponse + fun setMockConfig(mockConfig: MockConfig) { + mockConfig.content.fold( + ifLeft = { + mocks = getMocks(it) + }, + ifRight = { + mocks = it + }, + ) + } + + fun getSuccessResponse() = CompletionResult.Success(mocks.successResponse) + + fun getScanResponse() = CompletionResult.Success(mocks.scanResponse) + + fun getDerivationTaskResponse() = CompletionResult.Success(mocks.derivationTaskResponse) + + fun getCardDto() = CompletionResult.Success(mocks.cardDto) + + fun getExtendedPublicKey() = CompletionResult.Success(mocks.extendedPublicKey) + + fun getCreateProductWalletResponse(): CompletionResult { + return CompletionResult.Success(mocks.createProductWalletTaskResponse) + } + + fun getImportWalletResponse(): CompletionResult { + return CompletionResult.Success(mocks.importWalletResponse) + } private fun getMocks(productType: ProductType): Mocks { return when (productType) { @@ -17,4 +46,14 @@ object MockProvider { else -> TODO() } } + + // region Twin-specific + + fun finalizeTwin() = CompletionResult.Success(mocks.finalizeTwinResponse) + + fun createFirstTwinWallet() = CompletionResult.Success(mocks.createFirstTwinResponse) + + fun createSecondTwinWallet() = CompletionResult.Success(mocks.createSecondTwinResponse) + + // endregion } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/Mocks.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/Mocks.kt index a7de03220a..5aaa566f29 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/Mocks.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/Mocks.kt @@ -1,8 +1,32 @@ package com.tangem.tap.domain.sdk.mocks +import com.tangem.common.SuccessResponse +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse interface Mocks { + val successResponse: SuccessResponse + val scanResponse: ScanResponse + + val derivationTaskResponse: DerivationTaskResponse + + val cardDto: CardDTO + + val extendedPublicKey: ExtendedPublicKey + + val createProductWalletTaskResponse: CreateProductWalletTaskResponse + + val importWalletResponse: CreateProductWalletTaskResponse + + val finalizeTwinResponse: ScanResponse + + val createFirstTwinResponse: CreateWalletResponse + + val createSecondTwinResponse: CreateWalletResponse } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet/WalletMocks.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet/WalletMocks.kt index 2157f6fd60..3beb9d2dae 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet/WalletMocks.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet/WalletMocks.kt @@ -1,21 +1,49 @@ package com.tangem.tap.domain.sdk.mocks.wallet -import com.tangem.common.card.CardWallet -import com.tangem.common.card.EllipticCurve -import com.tangem.common.card.EncryptionMode -import com.tangem.common.card.FirmwareVersion +import com.tangem.common.SuccessResponse +import com.tangem.common.card.* +import com.tangem.common.extensions.ByteArrayKey import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse import com.tangem.operations.attestation.Attestation +import com.tangem.operations.backup.PrimaryCard +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.tap.domain.sdk.mocks.Mocks +import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import java.util.Date object WalletMocks : Mocks { - private val cardDto = CardDTO( + private val primaryCard = PrimaryCard( + cardId = "AC05000000086747", + batchId = "AC05", + cardPublicKey = byteArrayOf(2, -120, -3, -32, -122, -127, -120, -104, 59, 72, 76, 114, 94, 75, -37, -55, 55, 99, 66, 123, 85, -87, 80, 106, 105, -116, 87, -83, -12, 70, 108, -68, -39), + linkingKey = byteArrayOf( + 2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121, + -57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91, + ), + existingWalletsCount = 3, isHDWalletAllowed = true, + issuer = Card.Issuer( + name = "TANGEM AG", + publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2), + ), + walletCurves = listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519, EllipticCurve.Bls12381G2Aug), + firmwareVersion = FirmwareVersion( + major = 4, + minor = 52, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + isKeysImportAllowed = false, + certificate = null, + ) + + override val cardDto = CardDTO( cardId = "AC05000000086747", batchId = "AC05", cardPublicKey = byteArrayOf(2, -120, -3, -32, -122, -127, -120, -104, 59, 72, 76, 114, 94, 75, -37, -55, 55, 99, 66, 123, 85, -87, 80, 106, 105, -116, 87, -83, -12, 70, 108, -68, -39), @@ -124,4 +152,96 @@ object WalletMocks : Mocks { derivedKeys = emptyMap(), primaryCard = null, ) + + override val derivationTaskResponse = DerivationTaskResponse( + entries = mapOf( + ByteArrayKey( + byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge + publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), + chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + ) + + override val extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(3, 2, 95, 53, 40, -87, -60, 11, -8, -47, 41, 37, 100, 15, -69, 1, -122, 127, -20, -81, -32, -20, -24, 5, -28, 113, 106, -90, -59, -30, -27, -110, -110), + chainCode = byteArrayOf(-95, -87, -95, -25, 27, 96, -57, -92, -69, -106, -45, 10, 85, 4, -92, -68, 49, -24, -28, -50, -49, -77, -20, 118, -50, -27, 104, -93, 115, -50, -46, -34), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ) + + override val successResponse = SuccessResponse(cardId = "AC05000000086747") + + override val createProductWalletTaskResponse = CreateProductWalletTaskResponse( + card = cardDto, + derivedKeys = mapOf( + ByteArrayKey( + byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + primaryCard = primaryCard, + ) + + override val importWalletResponse: CreateProductWalletTaskResponse + get() = error("Available only for Wallet 2") + + override val createFirstTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val createSecondTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val finalizeTwinResponse: ScanResponse + get() = error("Available only for Twin") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet2/Wallet2Mocks.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet2/Wallet2Mocks.kt index 86b865896f..fca86fd05e 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet2/Wallet2Mocks.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet2/Wallet2Mocks.kt @@ -1,10 +1,43 @@ package com.tangem.tap.domain.sdk.mocks.wallet2 +import com.tangem.common.SuccessResponse +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.tap.domain.sdk.mocks.Mocks +import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse object Wallet2Mocks : Mocks { override val scanResponse: ScanResponse get() = TODO("Not yet implemented") + + override val cardDto: CardDTO + get() = TODO("Not yet implemented") + + override val derivationTaskResponse: DerivationTaskResponse + get() = TODO("Not yet implemented") + + override val extendedPublicKey: ExtendedPublicKey + get() = TODO("Not yet implemented") + + override val successResponse: SuccessResponse + get() = TODO("Not yet implemented") + + override val createProductWalletTaskResponse: CreateProductWalletTaskResponse + get() = TODO("Not yet implemented") + + override val importWalletResponse: CreateProductWalletTaskResponse + get() = TODO("Not yet implemented") + + override val createFirstTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val createSecondTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val finalizeTwinResponse: ScanResponse + get() = error("Available only for Twin") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index e203d7c4d5..34b7dada02 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -27,8 +27,7 @@ class TwinCardsManager( private val issuerKeyPair: KeyPair = getIssuerKeys(assetReader, card.issuer.publicKey.toHexString()) suspend fun createFirstWallet(message: Message): CompletionResult { - val response = tangemSdkManager.runTaskAsync( - runnable = CreateFirstTwinWalletTask(firstCardId), + val response = tangemSdkManager.createFirstTwinWallet( cardId = firstCardId, initialMessage = message, ) @@ -44,14 +43,15 @@ class TwinCardsManager( preparingMessage: Message, creatingWalletMessage: Message, ): CompletionResult { - val task = CreateSecondTwinWalletTask( + val response = tangemSdkManager.createSecondTwinWallet( firstPublicKey = currentCardPublicKey!!, firstCardId = firstCardId, issuerKeys = issuerKeyPair, preparingMessage = preparingMessage, creatingWalletMessage = creatingWalletMessage, + initialMessage = initialMessage, ) - val response = tangemSdkManager.runTaskAsync(task, null, initialMessage) + when (response) { is CompletionResult.Success -> { secondCardPublicKey = response.data.wallet.publicKey.toHexString() @@ -62,8 +62,9 @@ class TwinCardsManager( } suspend fun complete(message: Message): Result { - val response = tangemSdkManager.runTaskAsync( - runnable = FinalizeTwinTask(secondCardPublicKey!!.hexToBytes(), issuerKeyPair), + val response = tangemSdkManager.finalizeTwin( + secondCardPublicKey = secondCardPublicKey!!.hexToBytes(), + issuerKeyPair = issuerKeyPair, cardId = firstCardId, initialMessage = message, ) diff --git a/app/src/mocked/res/values/strings.xml b/app/src/mocked/res/values/strings.xml new file mode 100644 index 0000000000..f13e8b7c5e --- /dev/null +++ b/app/src/mocked/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + Mocked Tangem + + diff --git a/app/src/mocked/res/xml/network_security_config.xml b/app/src/mocked/res/xml/network_security_config.xml new file mode 100644 index 0000000000..894912ac61 --- /dev/null +++ b/app/src/mocked/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + + + + + + From 1b8eece37657058bfeec5e75f47cb2b0d7e01c22 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 09:19:53 +0800 Subject: [PATCH 30/98] Updated on 2026-08-14 --- .../tap/common/redux/global/GlobalReducer.kt | 1 - .../tap/data/RuntimeUserWalletsStore.kt | 18 ++++----- .../com/tangem/tap/di/AppStateHolderModule.kt | 5 --- .../tap/di/data/UserWalletsStoreModule.kt | 6 +-- .../tangem/tap/di/domain/CardDomainModule.kt | 6 +-- .../tap/di/domain/WalletsDomainModule.kt | 40 ++++++++++--------- .../di/WalletConnectInteractorModule.kt | 6 +-- .../domain/WalletConnectInteractor.kt | 6 +-- .../com/tangem/tap/proxy/AppStateHolder.kt | 17 +------- .../domain/wallets/legacy/UseCaseUtils.kt | 16 -------- .../wallets/legacy/WalletsStateHolder.kt | 12 ------ .../wallets/models/DeleteWalletError.kt | 4 +- .../wallets/models/GetUserWalletError.kt | 4 +- .../wallets/models/SelectWalletError.kt | 2 - .../wallets/usecase/DeleteWalletUseCase.kt | 12 ++---- .../usecase/GetSelectedWalletSyncUseCase.kt | 12 ++---- .../usecase/GetSelectedWalletUseCase.kt | 12 ++---- .../wallets/usecase/GetUserWalletUseCase.kt | 10 +---- .../wallets/usecase/GetWalletsUseCase.kt | 10 ++--- .../wallets/usecase/IsNeedToBackupUseCase.kt | 8 ++-- .../wallets/usecase/SaveWalletUseCase.kt | 12 ++---- .../wallets/usecase/SelectWalletUseCase.kt | 13 ++---- .../wallets/usecase/UnlockWalletsUseCase.kt | 8 ++-- .../wallets/usecase/UpdateWalletUseCase.kt | 12 ++---- .../feature/swap/DefaultSwapRepository.kt | 8 ++-- .../tangem/feature/swap/di/SwapDataModule.kt | 6 +-- .../swap/domain/di/SwapDomainModule.kt | 6 +-- 27 files changed, 87 insertions(+), 185 deletions(-) delete mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UseCaseUtils.kt delete mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/WalletsStateHolder.kt diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index b3e337e2bf..9cfcf4182c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -97,7 +97,6 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde is GlobalAction.UpdateUserWalletsListManager -> { val generalUserWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) - appStateHolder.userWalletsListManager = generalUserWalletsListManager globalState.copy(userWalletsListManager = generalUserWalletsListManager) } is GlobalAction.ChangeAppThemeMode -> globalState.copy( diff --git a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt index 87b5944231..bd50b28faf 100644 --- a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt +++ b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt @@ -1,7 +1,7 @@ package com.tangem.tap.data import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.firstOrNull @@ -9,26 +9,24 @@ import kotlinx.coroutines.flow.firstOrNull // FIXME: Workaround, remove it once the normal UserWalletsStore has been implemented // [REDACTED_JIRA] internal class RuntimeUserWalletsStore( - private val walletsStateHolder: WalletsStateHolder, + private val userWalletsListManager: UserWalletsListManager, ) : UserWalletsStore { override val selectedUserWalletOrNull: UserWallet? - get() = walletsStateHolder.userWalletsListManager?.selectedUserWalletSync + get() = userWalletsListManager.selectedUserWalletSync override suspend fun getSyncOrNull(key: UserWalletId): UserWallet? { - return walletsStateHolder.userWalletsListManager - ?.userWallets - ?.firstOrNull() + return userWalletsListManager + .userWallets + .firstOrNull() ?.singleOrNull { it.walletId == key } } override suspend fun getAllSyncOrNull(): List? { - return walletsStateHolder.userWalletsListManager - ?.userWallets - ?.firstOrNull() + return userWalletsListManager.userWallets.firstOrNull() } override suspend fun update(userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet) { - walletsStateHolder.userWalletsListManager?.update(userWalletId, update) + userWalletsListManager.update(userWalletId, update) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/AppStateHolderModule.kt b/app/src/main/java/com/tangem/tap/di/AppStateHolderModule.kt index e1bf363dd7..c6e0267c6b 100644 --- a/app/src/main/java/com/tangem/tap/di/AppStateHolderModule.kt +++ b/app/src/main/java/com/tangem/tap/di/AppStateHolderModule.kt @@ -2,7 +2,6 @@ package com.tangem.tap.di import com.tangem.core.navigation.ReduxNavController import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.tap.proxy.AppStateHolder import dagger.Binds import dagger.Module @@ -14,10 +13,6 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) internal interface AppStateHolderModule { - @Binds - @Singleton - fun bindsWalletsStateHolder(appStateHolder: AppStateHolder): WalletsStateHolder - @Binds @Singleton fun bindsNavigationStateHolder(appStateHolder: AppStateHolder): ReduxNavController diff --git a/app/src/main/java/com/tangem/tap/di/data/UserWalletsStoreModule.kt b/app/src/main/java/com/tangem/tap/di/data/UserWalletsStoreModule.kt index 6aeaad1dcf..b2a04f9d52 100644 --- a/app/src/main/java/com/tangem/tap/di/data/UserWalletsStoreModule.kt +++ b/app/src/main/java/com/tangem/tap/di/data/UserWalletsStoreModule.kt @@ -1,7 +1,7 @@ package com.tangem.tap.di.data import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.tap.data.RuntimeUserWalletsStore import dagger.Module import dagger.Provides @@ -15,7 +15,7 @@ internal object UserWalletsStoreModule { @Provides @Singleton - fun provideUserWalletsStore(walletsStateHolder: WalletsStateHolder): UserWalletsStore { - return RuntimeUserWalletsStore(walletsStateHolder) + fun provideUserWalletsStore(userWalletsListManager: UserWalletsListManager): UserWalletsStore { + return RuntimeUserWalletsStore(userWalletsListManager = userWalletsListManager) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index 6d9b682e61..4434311a95 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -6,7 +6,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.domain.demo.DemoConfig import com.tangem.domain.demo.IsDemoCardUseCase -import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase import dagger.Module import dagger.Provides @@ -58,8 +58,8 @@ internal object CardDomainModule { @Provides @ViewModelScoped - fun provideIsNeedToBackupUseCase(walletStateHolder: WalletsStateHolder): IsNeedToBackupUseCase { - return IsNeedToBackupUseCase(walletStateHolder) + fun provideIsNeedToBackupUseCase(userWalletsListManager: UserWalletsListManager): IsNeedToBackupUseCase { + return IsNeedToBackupUseCase(userWalletsListManager = userWalletsListManager) } @Provides diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index c7f58a8226..de5aa6d294 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -2,7 +2,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletAddressServiceRepository import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.* @@ -19,32 +19,34 @@ internal object WalletsDomainModule { @Provides @ViewModelScoped - fun providesGetWalletsUseCase(walletsStateHolder: WalletsStateHolder): GetWalletsUseCase { - return GetWalletsUseCase(walletsStateHolder = walletsStateHolder) + fun providesGetWalletsUseCase(userWalletsListManager: UserWalletsListManager): GetWalletsUseCase { + return GetWalletsUseCase(userWalletsListManager = userWalletsListManager) } @Provides @ViewModelScoped - fun providesGetUserWalletUseCase(walletsStateHolder: WalletsStateHolder): GetUserWalletUseCase { - return GetUserWalletUseCase(walletsStateHolder = walletsStateHolder) + fun providesGetUserWalletUseCase(userWalletsListManager: UserWalletsListManager): GetUserWalletUseCase { + return GetUserWalletUseCase(userWalletsListManager = userWalletsListManager) } @Provides @ViewModelScoped - fun providesGetSelectedWalletSyncUseCase(walletsStateHolder: WalletsStateHolder): GetSelectedWalletSyncUseCase { - return GetSelectedWalletSyncUseCase(walletsStateHolder = walletsStateHolder) + fun providesGetSelectedWalletSyncUseCase( + userWalletsListManager: UserWalletsListManager, + ): GetSelectedWalletSyncUseCase { + return GetSelectedWalletSyncUseCase(userWalletsListManager = userWalletsListManager) } @Provides @ViewModelScoped - fun providesGetSelectedWalletUseCase(walletsStateHolder: WalletsStateHolder): GetSelectedWalletUseCase { - return GetSelectedWalletUseCase(walletsStateHolder = walletsStateHolder) + fun providesGetSelectedWalletUseCase(userWalletsListManager: UserWalletsListManager): GetSelectedWalletUseCase { + return GetSelectedWalletUseCase(userWalletsListManager = userWalletsListManager) } @Provides @ViewModelScoped - fun providesSaveWalletUseCase(walletsStateHolder: WalletsStateHolder): SaveWalletUseCase { - return SaveWalletUseCase(walletsStateHolder = walletsStateHolder) + fun providesSaveWalletUseCase(userWalletsListManager: UserWalletsListManager): SaveWalletUseCase { + return SaveWalletUseCase(userWalletsListManager = userWalletsListManager) } @Provides @@ -55,29 +57,29 @@ internal object WalletsDomainModule { @Provides @ViewModelScoped - fun providesUnlockWalletUseCase(walletsStateHolder: WalletsStateHolder): UnlockWalletsUseCase { - return UnlockWalletsUseCase(walletsStateHolder = walletsStateHolder) + fun providesUnlockWalletUseCase(userWalletsListManager: UserWalletsListManager): UnlockWalletsUseCase { + return UnlockWalletsUseCase(userWalletsListManager = userWalletsListManager) } @Provides @ViewModelScoped fun providesSelectWalletUseCase( - walletsStateHolder: WalletsStateHolder, + userWalletsListManager: UserWalletsListManager, reduxStateHolder: ReduxStateHolder, ): SelectWalletUseCase { - return SelectWalletUseCase(walletsStateHolder, reduxStateHolder) + return SelectWalletUseCase(userWalletsListManager = userWalletsListManager, reduxStateHolder = reduxStateHolder) } @Provides @ViewModelScoped - fun providesUpdateWalletUseCase(walletsStateHolder: WalletsStateHolder): UpdateWalletUseCase { - return UpdateWalletUseCase(walletsStateHolder = walletsStateHolder) + fun providesUpdateWalletUseCase(userWalletsListManager: UserWalletsListManager): UpdateWalletUseCase { + return UpdateWalletUseCase(userWalletsListManager = userWalletsListManager) } @Provides @ViewModelScoped - fun providesDeleteWalletUseCase(walletsStateHolder: WalletsStateHolder): DeleteWalletUseCase { - return DeleteWalletUseCase(walletsStateHolder = walletsStateHolder) + fun providesDeleteWalletUseCase(userWalletsListManager: UserWalletsListManager): DeleteWalletUseCase { + return DeleteWalletUseCase(userWalletsListManager = userWalletsListManager) } @Provides diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt index 4fed4bf821..faaa951d2e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt @@ -8,7 +8,7 @@ import com.tangem.datasource.di.SdkMoshi import com.tangem.datasource.files.FileReader import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper import com.tangem.tap.domain.walletconnect2.app.WalletConnectEventsHandlerImpl @@ -40,7 +40,7 @@ internal object WalletConnectInteractorModule { walletConnectFeatureToggles: WalletConnectFeatureToggles, currenciesRepository: CurrenciesRepository, walletManagersFacade: WalletManagersFacade, - walletsStateHolder: WalletsStateHolder, + userWalletsListManager: UserWalletsListManager, ): WalletConnectInteractor { return WalletConnectInteractor( handler = WalletConnectEventsHandlerImpl(), @@ -50,7 +50,7 @@ internal object WalletConnectInteractorModule { blockchainHelper = TangemWcBlockchainHelper(walletConnectFeatureToggles), currenciesRepository = currenciesRepository, walletManagersFacade = walletManagersFacade, - walletsStateHolder = walletsStateHolder, + userWalletsListManager = userWalletsListManager, dispatchers = AppCoroutineDispatcherProvider(), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index d7b74b884b..e2e43d4975 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -6,7 +6,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.tap.common.extensions.filterNotNull @@ -31,12 +31,12 @@ class WalletConnectInteractor( private val dispatchers: CoroutineDispatcherProvider, private val walletManagersFacade: WalletManagersFacade, private val currenciesRepository: CurrenciesRepository, - private val walletsStateHolder: WalletsStateHolder, + private val userWalletsListManager: UserWalletsListManager, val blockchainHelper: WcBlockchainHelper, ) { private val getSelectedWalletUseCase by lazy(LazyThreadSafetyMode.NONE) { - GetSelectedWalletUseCase(walletsStateHolder) + GetSelectedWalletUseCase(userWalletsListManager) } private val wcScope = CoroutineScope( diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index d3965fa2cf..5faa769457 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -6,8 +6,6 @@ import com.tangem.core.navigation.ReduxNavController import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.models.UserWallet import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchWithMain @@ -15,8 +13,6 @@ import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.network.exchangeServices.ExchangeService -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow import org.rekotlin.Action import org.rekotlin.Store import javax.inject.Inject @@ -25,18 +21,7 @@ import javax.inject.Inject * Holds objects from old modules, that missing in DI graph. * Object sets manually to use in new modules and [AppStateHolder] proxies its to DI. */ -class AppStateHolder @Inject constructor() : WalletsStateHolder, ReduxNavController, ReduxStateHolder { - - override var userWalletsListManager: UserWalletsListManager? = null - set(value) { - field = value - _userWalletsListManagerFlow.value = value - } - - override val userWalletListManagerFlow: Flow - get() = _userWalletsListManagerFlow - - private val _userWalletsListManagerFlow = MutableStateFlow(null) +class AppStateHolder @Inject constructor() : ReduxNavController, ReduxStateHolder { @Deprecated("Use scan response from selected user wallet") var scanResponse: ScanResponse? = null diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UseCaseUtils.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UseCaseUtils.kt deleted file mode 100644 index ee7de756db..0000000000 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UseCaseUtils.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.domain.wallets.legacy - -import arrow.core.raise.Raise -import arrow.core.raise.ensureNotNull - -internal inline fun Raise.ensureUserWalletListManagerNotNull( - walletsStateHolder: WalletsStateHolder, - raise: (Throwable) -> Error, -): UserWalletsListManager { - return ensureNotNull( - value = walletsStateHolder.userWalletsListManager, - raise = { - raise(IllegalStateException("User wallets list manager not initialized")) - }, - ) -} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/WalletsStateHolder.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/WalletsStateHolder.kt deleted file mode 100644 index 37f6644beb..0000000000 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/WalletsStateHolder.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.domain.wallets.legacy - -import kotlinx.coroutines.flow.Flow - -// TODO: will be remove in this task [REDACTED_JIRA] -@Deprecated(message = "Provide UserWalletsListManager using DI", level = DeprecationLevel.WARNING) -interface WalletsStateHolder { - - val userWalletsListManager: UserWalletsListManager? - - val userWalletListManagerFlow: Flow -} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/DeleteWalletError.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/DeleteWalletError.kt index 29669fa19e..d58c220ac3 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/DeleteWalletError.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/DeleteWalletError.kt @@ -2,7 +2,5 @@ package com.tangem.domain.wallets.models sealed interface DeleteWalletError { - object DataError : DeleteWalletError - - object UnableToDelete : DeleteWalletError + data object UnableToDelete : DeleteWalletError } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/GetUserWalletError.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/GetUserWalletError.kt index 50d1bea262..3e7e62a562 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/GetUserWalletError.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/GetUserWalletError.kt @@ -2,7 +2,5 @@ package com.tangem.domain.wallets.models sealed class GetUserWalletError { - data class DataError(val cause: Throwable) : GetUserWalletError() - - object UserWalletNotFound : GetUserWalletError() + data object UserWalletNotFound : GetUserWalletError() } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/SelectWalletError.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/SelectWalletError.kt index a357f06ca9..e2aeab608f 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/SelectWalletError.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/SelectWalletError.kt @@ -2,7 +2,5 @@ package com.tangem.domain.wallets.models sealed interface SelectWalletError { - object DataError : SelectWalletError - object UnableToSelectUserWallet : SelectWalletError } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt index 2b43096bf4..59084184cc 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt @@ -6,27 +6,21 @@ import arrow.core.raise.either import arrow.core.right import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess -import com.tangem.domain.wallets.legacy.WalletsStateHolder -import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.DeleteWalletError import com.tangem.domain.wallets.models.UserWalletId /** * Use case for updating user wallet * - * @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager' + * @property userWalletsListManager user wallets list manager * [REDACTED_AUTHOR] */ -class DeleteWalletUseCase(private val walletsStateHolder: WalletsStateHolder) { +class DeleteWalletUseCase(private val userWalletsListManager: UserWalletsListManager) { suspend operator fun invoke(userWalletId: UserWalletId): Either { return either { - val userWalletsListManager = ensureUserWalletListManagerNotNull( - walletsStateHolder = walletsStateHolder, - raise = { DeleteWalletError.DataError }, - ) - userWalletsListManager.delete(userWalletIds = listOf(userWalletId)) .doOnSuccess { return Unit.right() } .doOnFailure { return DeleteWalletError.UnableToDelete.left() } diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletSyncUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletSyncUseCase.kt index 7cd6ffa8a2..e9f51327a6 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletSyncUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletSyncUseCase.kt @@ -3,8 +3,7 @@ package com.tangem.domain.wallets.usecase import arrow.core.Either import arrow.core.raise.either import arrow.core.raise.ensureNotNull -import com.tangem.domain.wallets.legacy.WalletsStateHolder -import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.GetUserWalletError import com.tangem.domain.wallets.models.UserWallet @@ -12,19 +11,14 @@ import com.tangem.domain.wallets.models.UserWallet * Use case for getting selected wallet. * Important! If all wallets is locked, use case returns a error. * - * @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager' + * @property userWalletsListManager user wallets list manager * [REDACTED_AUTHOR] */ -class GetSelectedWalletSyncUseCase(private val walletsStateHolder: WalletsStateHolder) { +class GetSelectedWalletSyncUseCase(private val userWalletsListManager: UserWalletsListManager) { operator fun invoke(): Either { return either { - val userWalletsListManager = ensureUserWalletListManagerNotNull( - walletsStateHolder = walletsStateHolder, - raise = GetUserWalletError::DataError, - ) - ensureNotNull( value = userWalletsListManager.selectedUserWalletSync, raise = { GetUserWalletError.UserWalletNotFound }, diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletUseCase.kt index ee23723206..bda3c953ee 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletUseCase.kt @@ -2,8 +2,7 @@ package com.tangem.domain.wallets.usecase import arrow.core.Either import arrow.core.raise.either -import com.tangem.domain.wallets.legacy.WalletsStateHolder -import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.GetUserWalletError import com.tangem.domain.wallets.models.UserWallet import kotlinx.coroutines.flow.Flow @@ -11,19 +10,14 @@ import kotlinx.coroutines.flow.Flow /** * Use case for getting flow of selected wallet. * - * @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager' + * @property userWalletsListManager user wallets list manager * [REDACTED_AUTHOR] */ -class GetSelectedWalletUseCase(private val walletsStateHolder: WalletsStateHolder) { +class GetSelectedWalletUseCase(private val userWalletsListManager: UserWalletsListManager) { operator fun invoke(): Either> { return either { - val userWalletsListManager = ensureUserWalletListManagerNotNull( - walletsStateHolder = walletsStateHolder, - raise = GetUserWalletError::DataError, - ) - userWalletsListManager.selectedUserWallet } } diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt index 2b9c40ae27..5d3d60350f 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt @@ -3,21 +3,15 @@ package com.tangem.domain.wallets.usecase import arrow.core.Either import arrow.core.raise.either import arrow.core.raise.ensureNotNull -import com.tangem.domain.wallets.legacy.WalletsStateHolder -import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.GetUserWalletError import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.firstOrNull -class GetUserWalletUseCase(private val walletsStateHolder: WalletsStateHolder) { +class GetUserWalletUseCase(private val userWalletsListManager: UserWalletsListManager) { suspend operator fun invoke(userWalletId: UserWalletId): Either = either { - val userWalletsListManager = ensureUserWalletListManagerNotNull( - walletsStateHolder = walletsStateHolder, - raise = GetUserWalletError::DataError, - ) - val userWallets = userWalletsListManager.userWallets.firstOrNull().orEmpty() ensureNotNull(userWallets.firstOrNull { it.walletId == userWalletId }) { diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt index 3c589f1bcd..76872f50ff 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt @@ -1,20 +1,18 @@ package com.tangem.domain.wallets.usecase -import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWallet import kotlinx.coroutines.flow.Flow /** * Use case for getting list of user wallets * - * @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager' + * @property userWalletsListManager user wallets list manager * [REDACTED_AUTHOR] */ -class GetWalletsUseCase(private val walletsStateHolder: WalletsStateHolder) { +class GetWalletsUseCase(private val userWalletsListManager: UserWalletsListManager) { @Throws(IllegalArgumentException::class) - operator fun invoke(): Flow> { - return requireNotNull(walletsStateHolder.userWalletsListManager).userWallets - } + operator fun invoke(): Flow> = userWalletsListManager.userWallets } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/IsNeedToBackupUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/IsNeedToBackupUseCase.kt index 24fdc5a232..e403101dd5 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/IsNeedToBackupUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/IsNeedToBackupUseCase.kt @@ -1,19 +1,19 @@ package com.tangem.domain.wallets.usecase import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map /** * Use case that checks if wallet need backup cards + * + * @property userWalletsListManager user wallets list manager */ -class IsNeedToBackupUseCase(private val walletsStateHolder: WalletsStateHolder) { +class IsNeedToBackupUseCase(private val userWalletsListManager: UserWalletsListManager) { operator fun invoke(id: UserWalletId): Flow { - val userWalletsListManager = requireNotNull(walletsStateHolder.userWalletsListManager) - return userWalletsListManager.userWallets .map { wallets -> val wallet = wallets.firstOrNull { it.walletId == id } diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt index 8e25c40c1e..0f3becefc0 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt @@ -7,27 +7,21 @@ import arrow.core.right import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.domain.wallets.legacy.UserWalletsListError -import com.tangem.domain.wallets.legacy.WalletsStateHolder -import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.SaveWalletError import com.tangem.domain.wallets.models.UserWallet /** * Use case for saving user wallet * - * @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager' + * @property userWalletsListManager user wallets list manager * [REDACTED_AUTHOR] */ -class SaveWalletUseCase(private val walletsStateHolder: WalletsStateHolder) { +class SaveWalletUseCase(private val userWalletsListManager: UserWalletsListManager) { suspend operator fun invoke(userWallet: UserWallet, canOverride: Boolean = false): Either { return either { - val userWalletsListManager = ensureUserWalletListManagerNotNull( - walletsStateHolder = walletsStateHolder, - raise = { SaveWalletError.DataError }, - ) - userWalletsListManager.save(userWallet, canOverride) .doOnSuccess { return Unit.right() } .doOnFailure { diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SelectWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SelectWalletUseCase.kt index 84303de548..da446a2a40 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SelectWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SelectWalletUseCase.kt @@ -5,8 +5,7 @@ import arrow.core.raise.either import arrow.core.right import com.tangem.common.CompletionResult import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.wallets.legacy.WalletsStateHolder -import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.SelectWalletError import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId @@ -14,22 +13,18 @@ import com.tangem.domain.wallets.models.UserWalletId /** * Use case for selecting wallet * - * @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager' + * @property userWalletsListManager user wallets list manager + * @property reduxStateHolder redux state holder * [REDACTED_AUTHOR] */ class SelectWalletUseCase( - private val walletsStateHolder: WalletsStateHolder, + private val userWalletsListManager: UserWalletsListManager, private val reduxStateHolder: ReduxStateHolder, ) { suspend operator fun invoke(userWalletId: UserWalletId): Either { return either { - val userWalletsListManager = ensureUserWalletListManagerNotNull( - walletsStateHolder = walletsStateHolder, - raise = { SelectWalletError.DataError }, - ) - return when (val result = userWalletsListManager.select(userWalletId)) { is CompletionResult.Failure -> raise(SelectWalletError.UnableToSelectUserWallet) is CompletionResult.Success -> { diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt index 9b6bdf4997..4d46212807 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt @@ -5,23 +5,23 @@ import arrow.core.raise.either import arrow.core.raise.ensureNotNull import com.tangem.common.doOnFailure import com.tangem.domain.wallets.legacy.UserWalletsListError +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType -import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.legacy.asLockable import com.tangem.domain.wallets.models.UnlockWalletsError /** * Unlock wallets use case * - * @property walletsStateHolder wallets state holder + * @property userWalletsListManager user wallets list manager * [REDACTED_AUTHOR] */ -class UnlockWalletsUseCase(private val walletsStateHolder: WalletsStateHolder) { +class UnlockWalletsUseCase(private val userWalletsListManager: UserWalletsListManager) { suspend operator fun invoke(type: UnlockType = UnlockType.ANY): Either = either { val userWalletsListManager = ensureNotNull( - value = walletsStateHolder.userWalletsListManager?.asLockable(), + value = userWalletsListManager.asLockable(), raise = { UnlockWalletsError.DataError( cause = IllegalStateException("The lockable user wallets list manager could not be found"), diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UpdateWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UpdateWalletUseCase.kt index 3a97779a39..b060418b2e 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UpdateWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UpdateWalletUseCase.kt @@ -6,8 +6,7 @@ import arrow.core.raise.either import arrow.core.right import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess -import com.tangem.domain.wallets.legacy.WalletsStateHolder -import com.tangem.domain.wallets.legacy.ensureUserWalletListManagerNotNull +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UpdateWalletError import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId @@ -15,22 +14,17 @@ import com.tangem.domain.wallets.models.UserWalletId /** * Use case for updating user wallet * - * @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager' + * @property userWalletsListManager user wallets list manager * [REDACTED_AUTHOR] */ -class UpdateWalletUseCase(private val walletsStateHolder: WalletsStateHolder) { +class UpdateWalletUseCase(private val userWalletsListManager: UserWalletsListManager) { suspend operator fun invoke( userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet, ): Either { return either { - val userWalletsListManager = ensureUserWalletListManagerNotNull( - walletsStateHolder = walletsStateHolder, - raise = { UpdateWalletError.DataError }, - ) - userWalletsListManager.update(userWalletId, update) .doOnSuccess { return it.right() } .doOnFailure { return UpdateWalletError.DataError.left() } diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt index 783ba7da25..66ff152f20 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt @@ -24,7 +24,7 @@ import com.tangem.datasource.crypto.DataSignatureVerifier import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.converters.* import com.tangem.feature.swap.domain.api.SwapRepository @@ -48,7 +48,7 @@ internal class DefaultSwapRepository @Inject constructor( private val tangemExpressApi: TangemExpressApi, private val coroutineDispatcher: CoroutineDispatcherProvider, private val walletManagersFacade: WalletManagersFacade, - private val walletsStateHolder: WalletsStateHolder, + private val userWalletsListManager: UserWalletsListManager, private val errorsDataConverter: ErrorsDataConverter, private val dataSignatureVerifier: DataSignatureVerifier, moshi: Moshi, @@ -389,8 +389,8 @@ internal class DefaultSwapRepository @Inject constructor( blockchain = blockchain, extraDerivationPath = null, derivationStyleProvider = requireNotNull( - walletsStateHolder.userWalletsListManager - ?.selectedUserWalletSync + userWalletsListManager + .selectedUserWalletSync ?.scanResponse ?.derivationStyleProvider, ), diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt index 95b8469b2a..98881e9a09 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt @@ -8,7 +8,7 @@ import com.tangem.datasource.crypto.DataSignatureVerifier import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.feature.swap.DefaultSwapRepository import com.tangem.feature.swap.DefaultSwapTransactionRepository import com.tangem.feature.swap.converters.ErrorsDataConverter @@ -33,7 +33,7 @@ internal class SwapDataModule { coroutineDispatcher: CoroutineDispatcherProvider, dataSignature: DataSignatureVerifier, walletManagerFacade: WalletManagersFacade, - walletsStateHolder: WalletsStateHolder, + userWalletsListManager: UserWalletsListManager, errorsDataConverter: ErrorsDataConverter, @NetworkMoshi moshi: Moshi, ): SwapRepository { @@ -42,7 +42,7 @@ internal class SwapDataModule { tangemExpressApi = tangemExpressApi, coroutineDispatcher = coroutineDispatcher, walletManagersFacade = walletManagerFacade, - walletsStateHolder = walletsStateHolder, + userWalletsListManager = userWalletsListManager, errorsDataConverter = errorsDataConverter, dataSignatureVerifier = dataSignature, moshi = moshi, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index f7ccce976e..a4b2c646ab 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -13,7 +13,7 @@ import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.transaction.usecase.SendTransactionUseCase import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.feature.swap.domain.* import com.tangem.feature.swap.domain.api.SwapRepository @@ -79,8 +79,8 @@ class SwapDomainModule { @SwapScope @Provides @Singleton - fun providesGetSelectedWalletUseCase(walletsStateHolder: WalletsStateHolder): GetSelectedWalletSyncUseCase { - return GetSelectedWalletSyncUseCase(walletsStateHolder = walletsStateHolder) + fun providesGetSelectedWalletUseCase(userWalletsListManager: UserWalletsListManager): GetSelectedWalletSyncUseCase { + return GetSelectedWalletSyncUseCase(userWalletsListManager = userWalletsListManager) } @Provides From c21421f5d52019efe5ecc271d1439b8ce62b07c0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 22 Apr 2024 16:20:10 +0500 Subject: [PATCH 31/98] Updated on 2026-08-14 --- app/build.gradle.kts | 1 - app/src/main/AndroidManifest.xml | 1 - .../java/com/tangem/tap/TangemApplication.kt | 2 - .../appsFlyer/AppsFlyerAnalyticsHandler.kt | 38 ------------------- .../handlers/appsFlyer/AppsFlyerClient.kt | 27 ------------- .../handlers/appsFlyer/AppsFlyerLogClient.kt | 18 --------- .../datasource/config/ConfigManagerImpl.kt | 1 - .../tangem/datasource/config/models/Config.kt | 1 - .../datasource/config/models/JsonModels.kt | 6 --- .../com/tangem/domain/common/LogConfig.kt | 1 - gradle/dependencies.toml | 2 - 11 files changed, 98 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerLogClient.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a4c7cd0cfd..f775c46b33 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -183,7 +183,6 @@ dependencies { implementation(deps.reKotlin) implementation(deps.zxing.qrCore) implementation(deps.coil) - implementation(deps.appsflyer) implementation(deps.amplitude) implementation(deps.kotsonGson) implementation(deps.spongecastle.core) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d547bbdf53..1d52735ce7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,7 +10,6 @@ - diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 690919b008..c6f72f4025 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -45,7 +45,6 @@ import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler -import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler import com.tangem.tap.common.chat.ChatManager import com.tangem.tap.common.feedback.AdditionalFeedbackInfo @@ -282,7 +281,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private fun initAnalytics(application: Application, config: Config) { val factory = AnalyticsFactory() factory.addHandlerBuilder(AmplitudeAnalyticsHandler.Builder()) - factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder()) factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder()) factory.addFilter(oneTimeEventFilter) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt deleted file mode 100644 index 710c7308e6..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.tangem.tap.common.analytics.handlers.appsFlyer - -import com.appsflyer.AFInAppEventType -import com.tangem.core.analytics.api.AnalyticsHandler -import com.tangem.core.analytics.models.AnalyticsEvent -import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder -import com.tangem.tap.common.analytics.events.Shop - -class AppsFlyerAnalyticsHandler( - private val client: AppsFlyerAnalyticsClient, -) : AnalyticsHandler { - - override fun id(): String = ID - - override fun send(eventId: String, params: Map) { - client.logEvent(eventId, params) - } - - override fun send(event: AnalyticsEvent) { - if (event is Shop.Purchased) { - send(AFInAppEventType.PURCHASE, event.params) - } else { - super.send(event) - } - } - - companion object { - const val ID = "AppsFlyer" - } - - class Builder : AnalyticsHandlerBuilder { - override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when { - !data.isDebug -> AppsFlyerClient(data.application, data.config.appsFlyerDevKey) - data.isDebug && data.logConfig.appsFlyer -> AppsFlyerLogClient(data.jsonConverter) - else -> null - }?.let { AppsFlyerAnalyticsHandler(it) } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt deleted file mode 100644 index 40cee4e698..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.tap.common.analytics.handlers.appsFlyer - -import android.content.Context -import com.appsflyer.AppsFlyerLib -import com.tangem.core.analytics.api.EventLogger - -/** -[REDACTED_AUTHOR] - */ -interface AppsFlyerAnalyticsClient : EventLogger - -internal class AppsFlyerClient( - private val context: Context, - key: String, -) : AppsFlyerAnalyticsClient { - - private val appsFlyerLib: AppsFlyerLib = AppsFlyerLib.getInstance() - - init { - appsFlyerLib.init(key, null, context) - appsFlyerLib.start(context) - } - - override fun logEvent(event: String, params: Map) { - appsFlyerLib.logEvent(context, event, params) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerLogClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerLogClient.kt deleted file mode 100644 index 8e87061b2c..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerLogClient.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.tap.common.analytics.handlers.appsFlyer - -import com.tangem.common.json.MoshiJsonConverter -import com.tangem.tap.common.analytics.AnalyticsEventsLogger - -/** -[REDACTED_AUTHOR] - */ -internal class AppsFlyerLogClient( - jsonConverter: MoshiJsonConverter, -) : AppsFlyerAnalyticsClient { - - private val logger = AnalyticsEventsLogger(AppsFlyerAnalyticsHandler.ID, jsonConverter) - - override fun logEvent(event: String, params: Map) { - logger.logEvent(event, params) - } -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt index 32ea540a36..f7565b73c2 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt @@ -100,7 +100,6 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager { chiaFireAcademyApiKey = configValues.chiaFireAcademyApiKey, chiaTangemApiKey = configValues.chiaTangemApiKey, ), - appsFlyerDevKey = configValues.appsFlyer.appsFlyerDevKey, amplitudeApiKey = configValues.amplitudeApiKey, sprinklr = configValues.sprinklr, walletConnectProjectId = configValues.walletConnectProjectId, diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt index 25e8595d33..28e5b2b4d3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt @@ -8,7 +8,6 @@ data class Config( val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C", val mercuryoWidgetId: String = "", val mercuryoSecret: String = "", - val appsFlyerDevKey: String = "", val amplitudeApiKey: String = "", val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(), val isTopUpEnabled: Boolean = false, diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt index 4071534469..5d2560be9e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt @@ -31,7 +31,6 @@ class ConfigValueModel( @Json(name = "tonCenterApiKey") val tonCenterKeys: TonCenterKeys, val blockcypherTokens: Set?, val infuraProjectId: String?, - val appsFlyer: AppsFlyer, val sprinklr: SprinklrConfig?, val tronGridApiKey: String, val amplitudeApiKey: String, @@ -81,11 +80,6 @@ data class GetBlockToken( @Json(name = "rosetta") val rosetta: String?, ) -data class AppsFlyer( - val appsFlyerDevKey: String, - val appsFlyerAppID: String, -) - class ConfigModel( val features: FeatureModel?, val configValues: ConfigValueModel?, diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/LogConfig.kt b/domain/legacy/src/main/java/com/tangem/domain/common/LogConfig.kt index 1f0b7b7b51..9373ae2a5b 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/LogConfig.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/LogConfig.kt @@ -20,6 +20,5 @@ object NetworkLogConfig { object AnalyticsHandlersLogConfig { const val firebase: Boolean = false - const val appsFlyer: Boolean = false val amplitude: Boolean = BuildConfig.LOG_ENABLED } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index c246ab9f05..3c5bee2404 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -39,7 +39,6 @@ compose-lifecycle-runtime = "2.7.0" # region Other libraries amplitude = "2.36.1" -appsflyer = "6.5.1" armadillo = "0.9.0" coil = "2.1.0" compose-shimmer = "1.0.3" @@ -204,7 +203,6 @@ test-hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", versi # region Other amplitude = { module = "com.amplitude:android-sdk", version.ref = "amplitude" } -appsflyer = { module = "com.appsflyer:af-android-sdk", version.ref = "appsflyer" } armadillo = { module = "at.favre.lib:armadillo", version.ref = "armadillo" } coil = { module = "io.coil-kt:coil", version.ref = "coil" } kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutine" } From df066bbbfd78b145cef17ecb54cfebbd1f50dfac Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Apr 2024 14:14:51 +0500 Subject: [PATCH 32/98] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- jitpack.gradle | 4 ---- settings.gradle.kts | 48 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 45 insertions(+), 9 deletions(-) delete mode 100644 jitpack.gradle diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 3c5bee2404..15baa2f024 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ decompose = "2.2.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-601" +tangemBlockchainSdk = "develop-603" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-346" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/jitpack.gradle b/jitpack.gradle deleted file mode 100644 index cd263447fd..0000000000 --- a/jitpack.gradle +++ /dev/null @@ -1,4 +0,0 @@ -ext.jitpackSdk = [ - group: 'com.github.Tangem', - version : '0.2.1', -] \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 1a729a943f..e297edfac7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,11 +24,41 @@ dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { - google() + google { + content { + includeGroupAndSubgroups("androidx") + includeGroupAndSubgroups("com.android") + includeGroupAndSubgroups("com.google") + } + } mavenCentral() - mavenLocal() - jcenter() // unable to replace with mavenCentral() due to rekotlin - maven("https://nexus.tangem-tech.com/repository/maven-releases/") + mavenLocal { + content { + includeGroupAndSubgroups("com.tangem.tangem-sdk-kotlin") + includeModule("com.tangem", "blstlib") + includeModule("com.tangem", "blockchain") + includeModule("com.tangem", "wallet-core-proto") + includeModule("com.tangem", "wallet-core") + } + } + maven { + // setting any repository from tangem project allows maven search all packages in the project + url = uri("https://maven.pkg.github.com/tangem/tangem-sdk-android") + credentials { + username = properties.getProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = properties.getProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + content { includeGroupAndSubgroups("com.tangem.tangem-sdk-kotlin") } + } + maven { + // setting any repository from tangem project allows maven search all packages in the project + url = uri("https://maven.pkg.github.com/tangem/blst-android") + credentials { + username = properties.getProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = properties.getProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + content { includeModule("com.tangem", "blstlib") } + } maven { // setting any repository from tangem project allows maven search all packages in the project url = uri("https://maven.pkg.github.com/tangem/blockchain-sdk-kotlin") @@ -36,6 +66,7 @@ dependencyResolutionManagement { username = properties.getProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") password = properties.getProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") } + content { includeModule("com.tangem", "blockchain") } } maven { // setting any repository from tangem project allows maven search all packages in the project @@ -44,6 +75,15 @@ dependencyResolutionManagement { username = properties.getProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") password = properties.getProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") } + content { + includeModule("com.tangem", "wallet-core-proto") + includeModule("com.tangem", "wallet-core") + } + } + jcenter { // unable to replace with mavenCentral() due to rekotlin + content { + includeModule("org.rekotlin", "rekotlin") + } } maven("https://jitpack.io") maven("https://clients-nexus.sprinklr.com/") From 385cc79a32152875be1e931d4eec1f0149a6b6df Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Apr 2024 12:21:21 +0200 Subject: [PATCH 33/98] Updated on 2026-08-14 --- .../src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index eaf4ddfa6b..a74ac1059c 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -433,7 +433,7 @@ internal class StateBuilder( SwapWarning.NeedReserveToCreateAccount( notificationConfig = createActivateAccountNotificationConfig( status.amountToCreateAccount, - quoteModel.toTokenInfo.cryptoCurrencyStatus.currency.name, + quoteModel.toTokenInfo.cryptoCurrencyStatus.currency.symbol, ), ), ) From a3f2c421bffed3ca8794729e661e3b0b2a7d7d7c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Apr 2024 15:56:47 +0100 Subject: [PATCH 34/98] Updated on 2026-08-14 --- .../converters/AnalyticsErrorConverter.kt | 2 +- .../tap/di/domain/TransactionDomainModule.kt | 12 +- .../redux/middlewares/RequestFeeMiddleware.kt | 2 +- .../send/redux/middlewares/SendMiddleware.kt | 2 +- .../tap/features/send/ui/SendFragment.kt | 101 ++++++------ .../moonpay/MoonPayService.kt | 2 +- .../deeplink/global/SellCurrencyDeepLink.kt | 2 + .../configs/feature_toggles_config.json | 2 +- core/res/src/main/res/values-ru/strings.xml | 32 +++- core/res/src/main/res/values/strings.xml | 26 +++- .../segmentedbutton/SegmentedButton.kt | 14 +- .../components/currency/fiaticon/FiatIcon.kt | 17 ++- .../CryptoCurrencyToIconStateConverter.kt | 45 +++++- .../components/inputrow/inner/PasteButton.kt | 2 +- .../transactions/TransactionDoneTitle.kt | 8 +- .../core/ui/utils/BigDecimalFormatter.kt | 23 +++ .../com/tangem/core/ui/utils/DateUtils.kt | 2 +- .../DefaultQrScanningEventsRepository.kt | 13 +- domain/demo/build.gradle.kts | 1 + .../domain}/demo/DemoTransactionSender.kt | 2 +- .../domain/tokens/model/CryptoCurrency.kt | 3 +- .../transaction/error/SendTransactionError.kt | 4 +- .../transaction/usecase/GetFeeUseCase.kt | 37 ++++- .../usecase/SendTransactionUseCase.kt | 38 ++++- .../wallet2/ui/YourSeedPhraseScreen.kt | 2 +- .../feature/qrscanning/QrScanningFragment.kt | 20 ++- .../viewmodel/BaseQrScanningClickIntents.kt | 12 +- .../viewmodel/QrScanningClickIntents.kt | 17 ++- .../viewmodel/QrScanningViewModel.kt | 6 +- .../feature/referral/ui/ReferralScreen.kt | 7 +- .../analytics/SendAnalyticEvents.kt | 4 + .../impl/presentation/state/SendAlertState.kt | 10 -- .../state/SendEventStateFactory.kt | 17 +-- .../presentation/state/SendNotification.kt | 10 +- .../presentation/state/SendStateFactory.kt | 16 +- .../impl/presentation/state/SendUiState.kt | 5 +- .../state/amount/AmountStateFactory.kt | 16 -- .../state/amount/SendAmountStateConverter.kt | 35 +++-- .../amount/SendAmountSubtractConverter.kt | 48 ------ .../confirm/SendConfirmStateConverter.kt | 1 - .../state/confirm/SendNotificationFactory.kt | 144 ++++++++++-------- .../presentation/state/fee/FeeCalculation.kt | 82 ++++++++-- .../state/fee/FeeSelectorState.kt | 2 + .../presentation/state/fee/FeeStateFactory.kt | 8 +- .../state/fee/SendFeeStateConverter.kt | 2 +- .../fields/SendAmountFieldChangeConverter.kt | 1 - .../SendAmountFieldMaxAmountConverter.kt | 1 - .../previewdata/AmountStatePreviewData.kt | 4 +- .../state/previewdata/SendClickIntentsStub.kt | 4 +- .../SendRecipientHistoryListConverter.kt | 4 +- .../SendRecipientWalletListConverter.kt | 35 +++-- .../presentation/ui/SendNavigationButtons.kt | 3 +- .../presentation/ui/amount/AmountButtons.kt | 28 ++-- .../presentation/ui/amount/AmountField.kt | 13 +- .../ui/amount/AmountFieldContainer.kt | 1 - .../ui/amount/SendAmountContent.kt | 4 +- .../impl/presentation/ui/send/FeeBlock.kt | 93 ++++++++--- .../presentation/ui/send/RecipientBlock.kt | 24 +++ .../viewmodel/SendClickIntents.kt | 4 +- .../presentation/viewmodel/SendViewModel.kt | 91 ++++------- .../swap/domain/di/SwapDomainModule.kt | 5 +- .../tangem/feature/swap/ui/StateBuilder.kt | 20 ++- ...enDetailsSwapTransactionsStateConverter.kt | 4 +- .../TokenDetailsTxHistoryItemFlowConverter.kt | 6 +- .../viewmodels/TokenDetailsViewModel.kt | 16 -- .../deeplink/WalletDeepLinksHandler.kt | 59 ++++--- .../converter/TxHistoryItemFlowConverter.kt | 6 +- .../wallet/viewmodels/WalletViewModel.kt | 2 +- .../com/tangem/lib/crypto/BlockchainUtils.kt | 7 + 69 files changed, 749 insertions(+), 542 deletions(-) rename {app/src/main/java/com/tangem/tap/features => domain/demo/src/main/java/com/tangem/domain}/demo/DemoTransactionSender.kt (98%) delete mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt index b8e9b2e458..4f32eee854 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt @@ -2,8 +2,8 @@ package com.tangem.tap.common.analytics.converters import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.common.core.TangemSdkError +import com.tangem.domain.demo.DemoTransactionSender import com.tangem.tap.common.analytics.events.AnalyticsParam -import com.tangem.tap.features.demo.DemoTransactionSender import com.tangem.utils.converter.Converter /** diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index 5fc760156f..bf4f00059e 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -1,7 +1,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.domain.demo.IsDemoCardUseCase +import com.tangem.domain.demo.DemoConfig import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.transaction.usecase.CreateTransactionUseCase @@ -22,20 +22,24 @@ internal object TransactionDomainModule { @Provides @ViewModelScoped fun provideGetFeeUseCase(walletManagersFacade: WalletManagersFacade): GetFeeUseCase { - return GetFeeUseCase(walletManagersFacade) + return GetFeeUseCase( + walletManagersFacade = walletManagersFacade, + demoConfig = DemoConfig(), + ) } @Provides @ViewModelScoped fun provideSendTransactionUseCase( - isDemoCardUseCase: IsDemoCardUseCase, cardSdkConfigRepository: CardSdkConfigRepository, transactionRepository: TransactionRepository, + walletManagersFacade: WalletManagersFacade, ): SendTransactionUseCase { return SendTransactionUseCase( - isDemoCardUseCase = isDemoCardUseCase, + demoConfig = DemoConfig(), cardSdkConfigRepository = cardSdkConfigRepository, transactionRepository = transactionRepository, + walletManagersFacade = walletManagersFacade, ) } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt index 2cb96aa486..cfdcf74a19 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt @@ -7,7 +7,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.common.extensions.isZero import com.tangem.tap.common.redux.AppState -import com.tangem.tap.features.demo.DemoTransactionSender +import com.tangem.domain.demo.DemoTransactionSender import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.send.redux.AmountActionUi import com.tangem.tap.features.send.redux.FeeAction diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 2922bd5b22..519637bd2f 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -31,7 +31,7 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TangemSigner import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage -import com.tangem.tap.features.demo.DemoTransactionSender +import com.tangem.domain.demo.DemoTransactionSender import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.FeeAction.RequestFee diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 0402c5e2de..23484d916d 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -13,8 +13,8 @@ import androidx.core.view.postDelayed import androidx.core.widget.addTextChangedListener import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle -import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import arrow.core.getOrElse @@ -54,14 +54,13 @@ import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.ui.adapters.WarningMessagesAdapter import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber -import com.tangem.tap.mainScope import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentSendBinding import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber @@ -73,6 +72,7 @@ private const val EDIT_TEXT_INPUT_DEBOUNCE = 400L /** [REDACTED_AUTHOR] */ +@Suppress("LargeClass") @OptIn(FlowPreview::class) @AndroidEntryPoint class SendFragment : BaseStoreFragment(R.layout.fragment_send) { @@ -103,18 +103,25 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { lifecycle.addObserver(viewModel) sendSubscriber.initViewModel(viewModel) Analytics.send(Token.Send.ScreenOpened()) - listenToQrCode() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + subscribeToQrCodeScanner() + subscribeToTransactionExtrasFields() + subscribeToAddressField() + subscribeToAmountField() + } + } + addBackPressHandler(this) etAmountToSend = view.findViewById(R.id.etAmountToSend) initSendButtonStates() setupAddressLayout() - setupTransactionExtrasLayout() setupAmountLayout() setupFeeLayout() setupWarningMessages() @@ -149,14 +156,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { setOnFocusChangeListener { _, hasFocus -> store.dispatch(TruncateOrRestore(!hasFocus)) } - - inputtedTextAsFlow() - .debounce(EDIT_TEXT_INPUT_DEBOUNCE) - .filter { store.state.sendState.addressState.viewFieldValue.value != it } - .onEach { - store.dispatch(AddressActionUi.HandleUserInput(it)) - } - .launchIn(mainScope) } imvPaste.setOnClickListener { @@ -183,31 +182,41 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { } } - private fun listenToQrCode() { - lifecycleScope.launch { - listenToQrScanningUseCase(SourceType.SEND) - .getOrElse { emptyFlow() } - .flowWithLifecycle(this@SendFragment.lifecycle, minActiveState = Lifecycle.State.CREATED) - .collect { rawQr -> - delay(200) - - // Delayed launch is needed in order for the UI to be drawn and to process the sent events. - // If do not use the delay, then etAmount error field is not displayed when - // inserting an incorrect amount by shareUri - cryptoCurrency?.let { cryptoCurrency -> - parseQrCodeUseCase(rawQr, cryptoCurrency = cryptoCurrency).fold( - ifLeft = { - onCodeScanned(QrResult(address = rawQr)) - Timber.w(it) - }, - ifRight = { onCodeScanned(it) }, - ) - } ?: onCodeScanned(QrResult(address = rawQr)) - } - } + private fun CoroutineScope.subscribeToAddressField() = with(binding.lSendAddress) { + etAddress.inputtedTextAsFlow() + .debounce(EDIT_TEXT_INPUT_DEBOUNCE) + .filter { store.state.sendState.addressState.viewFieldValue.value != it } + .onEach { + store.dispatch(AddressActionUi.HandleUserInput(it)) + } + .launchIn(this@subscribeToAddressField) } - private fun setupTransactionExtrasLayout() = with(binding.lSendAddress) { + private fun CoroutineScope.subscribeToAmountField() { + etAmountToSend.inputtedTextAsFlow() + .debounce(EDIT_TEXT_INPUT_DEBOUNCE) + .filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() } + .onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) } + .launchIn(this) + } + + private fun CoroutineScope.subscribeToQrCodeScanner() { + listenToQrScanningUseCase(SourceType.SEND) + .getOrElse { emptyFlow() } + .onEach { rawQr -> + cryptoCurrency?.let { cryptoCurrency -> + parseQrCodeUseCase(rawQr, cryptoCurrency = cryptoCurrency).fold( + ifLeft = { + onCodeScanned(QrResult(address = rawQr)) + Timber.w(it) + }, + ifRight = { onCodeScanned(it) }, + ) + } ?: onCodeScanned(QrResult(address = rawQr)) + }.launchIn(this) + } + + private fun CoroutineScope.subscribeToTransactionExtrasFields() = with(binding.lSendAddress) { // TODO: [REDACTED_TASK_KEY] etXlmMemo.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -216,7 +225,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.xlmMemo?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.XlmMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) etDestinationTag.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -225,7 +234,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.xrpDestinationTag?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.XrpDestinationTag.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) etBinanceMemo.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -234,7 +243,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.binanceMemo?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.BinanceMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) etTonMemo.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -243,7 +252,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.tonMemoState?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.TonMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) etCosmosMemo.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -252,7 +261,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.cosmosMemoState?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.CosmosMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) etHederaMemo.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -261,7 +270,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.hederaMemoState?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.HederaMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) etAlgorandMemo.inputtedTextAsFlow() .debounce(EDIT_TEXT_INPUT_DEBOUNCE) @@ -270,7 +279,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { info.algorandMemoState?.viewFieldValue?.value != it } .onEach { store.dispatch(TransactionExtrasAction.AlgorandMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .launchIn(this@subscribeToTransactionExtrasFields) } private fun onCodeScanned(parsedQr: QrResult) { @@ -343,12 +352,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { if (!hasFocus && etAmountToSend.text?.toString() == "") etAmountToSend.setText("0") } - etAmountToSend.inputtedTextAsFlow() - .debounce(EDIT_TEXT_INPUT_DEBOUNCE) - .filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() } - .onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) } - .launchIn(mainScope) - etAmountToSend.setOnImeActionListener(EditorInfo.IME_ACTION_DONE) { it.hideSoftKeyboard() it.clearFocus() diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt index 228273c9bd..c004c38b21 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt @@ -135,7 +135,7 @@ class MoonPayService( .appendQueryParameter("apiKey", apiKey) .appendQueryParameter("baseCurrencyCode", moonpayCurrency.currencyCode.uppercase()) .appendQueryParameter("refundWalletAddress", walletAddress) - .appendQueryParameter("redirectURL", "tangem://redirect_sell") + .appendQueryParameter("redirectURL", "tangem://redirect_sell?currency_id=${cryptoCurrency.id.value}") if (isDarkTheme) uri.appendQueryParameter("theme", "dark") diff --git a/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt index 579c72689a..db28975f63 100644 --- a/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt +++ b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt @@ -11,6 +11,7 @@ class SellCurrencyDeepLink(val onReceive: (data: Data) -> Unit) : DeepLink { transactionId = params["transactionId"] ?: return, baseCurrencyAmount = params["baseCurrencyAmount"] ?: return, depositWalletAddress = params["depositWalletAddress"] ?: return, + currencyId = params["currency_id"] ?: return, depositWalletAddressTag = params["depositWalletAddressTag"], ) @@ -21,6 +22,7 @@ class SellCurrencyDeepLink(val onReceive: (data: Data) -> Unit) : DeepLink { val transactionId: String, val baseCurrencyAmount: String, val depositWalletAddress: String, + val currencyId: String, val depositWalletAddressTag: String?, ) } \ No newline at end of file diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index d5cd06e891..b3f4ebb779 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -9,7 +9,7 @@ }, { "name": "REDESIGNED_SEND_SCREEN_ENABLED", - "version": "5.9.0" + "version": "5.9.1" }, { "name": "LOCAL_USER_LOGS_ENABLED", diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 1e87046df0..da22a118ee 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -4,12 +4,16 @@ Валюты Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств. Обратиться в поддержку + Попробовать снова Эта функция недоступна в демонстрационном режиме Причина: %s Не могу отправить транзакцию Выбранный кошелёк не поддерживает сеть %1$s Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. + Спасибо за ваш отзыв. Мы ответим в кратчайшие сроки. + Ваши предложения отправлены + Пожалуйста, попробуйте приложить карту в точности, как показано на анимации, или запросите поддержку. У вас возникли трудности со сканированием карты? Эта карта не предназначена для работы с этим приложением Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. @@ -24,6 +28,8 @@ Тёмная Светлая Как в системе + При выборе настройки как в системе приложение будет использовать тему в соответствии с настройками вашего устройства + Системная Тема Настройки приложения Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" @@ -51,6 +57,10 @@ Заводские настройки Тип безопасности Настройки карты + Ввиду особенностей сети Cardano при транзакции токена %1$s помимо комиссии сети будет списано %2$s + Для совершения транзакции %1$s, вам необходимо внести немного %2$s (%3$s), чтобы покрыть комиссию сети и минимальное значение ADA для отправки. + Недостаточно ADA для отправки токена + Вывод всего баланса ADA невозможен при наличии средств на токенах сети Cardano. Сначала выведите средства на ваших токенах. Принять Доступ запрещен Применить @@ -71,6 +81,7 @@ Создать Удалить Отключено + Отключить Готово Включить Включено @@ -96,12 +107,14 @@ Нет адреса OK Основная карта - Кодовая фраза + Парольная фраза + Вставить Подробнее Получить Отклонить Перезагрузить Переименовать + Повторить Сохранить изменения Искать Поиск токенов @@ -125,6 +138,7 @@ Я понял Произошла ошибка. Пожалуйста, попробуйте снова. Недоступно + Предупреждение Да Адрес контракта скопирован! Доступные сети @@ -171,6 +185,7 @@ Скрывать балансы жестом переворота Эмитент Подписано + Если вы забудете код, то потеряете доступ к своим средствам. Восстановление кода невозможно. Подробности Проверьте подключение с интернетом или переключитесь на другую сеть Условия использования @@ -183,9 +198,9 @@ Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами Выберите провайдера Произошла ошибка. Код: %s - К сожалению обмен указанной пары через выбранного провайдера на данный момент не возможен. Попробуйте совершить обмен позже. (Код: %s) + К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) Выбранный провайдер не доступен для обмена. Попробуйте позже. (Код: %s) - В данный момент обмен не возможен. Попробуйте позже. (Код: %s) + В данный момент обмен невозможен. Попробуйте позже. (Код: %s) Курс обмена Обмен через %s Чтобы вернуть ваши деньги, посетите сайт провайдера @@ -288,7 +303,7 @@ Ошибка активации Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. - Кодовая фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. + Парольная фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. Добавить резервную карту Сканировать карту #%d Создать резервную копию @@ -425,8 +440,6 @@ Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. Приготовьте свою карту Уже содержится в введенном адресе - Вычесть - Недостаточно средств для покрытия комиссии сети. Вычесть недостающую сумму для покрытия комиссии из отправляемой суммы? Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? Причина: %1$s\nКод: %2$s @@ -478,7 +491,7 @@ Возможны задержки по транзакции Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. Лимит транзакции - Необязательное + Опционально Пожалуйста, совместите свой QR-код с квадратом, чтобы отсканировать его. Убедитесь, что вы сканируете адрес в сети %s. Последние Получатель @@ -543,6 +556,7 @@ Выбранная операция в данный момент недоступна. Попробуйте позже. Обмен %s не доступен. Но мы работаем над его добавлением. В данный момент продажа монеты %s недоступна. Но мы работаем над её добавлением. + Выберите адрес Сгенерировать XPUB Скрыть Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. @@ -652,6 +666,10 @@ Пожалуйста, измените сумму для обмена Возможно, данная карта - образец или подделка Ошибка проверки подлинности + Ассоциировать + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%.4f %s + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять + Ассоциируете свой токен На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. Малое количество подписей Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index f0b93b187c..efe98087bb 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -4,12 +4,16 @@ Manage tokens Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. Request support + Try again This feature is disabled in Demo mode Reason: %s Can\'t send a transaction The selected does not support the %1$s network To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. Tokens in %1$s network are not supported by this card due to firmware limitation. + Thank you for your feedback. We will respond as soon as possible + Your suggestions were sent + Please try to tap the card exactly as shown in the animation or request support. Are you having difficulty scanning your card? This card is not designed to work with this app Default Fee @@ -25,6 +29,8 @@ Dark Light System default + If system is selected, the app will auto-adjust based on your device\'s system settings + System Theme App Settings To hide or show your balances, simply flip your device screen down, or switch it off in Settings @@ -50,6 +56,10 @@ Reset to Factory Settings Security Mode Card Settings + Due to the peculiarities of the Cardano network, when transacting the %1$s token, in addition to the network commission, %2$s will be charged + To make a %1$s transaction, you must deposit some %2$s (%3$s) to cover the network fee and minimum ADA value + Insufficient ADA to token transfer + Withdrawal of the entire ADA balance is not possible if funds are available in Cardano network tokens. First, withdraw funds on your tokens. Accept Access denied Apply @@ -70,6 +80,7 @@ Create Delete Disabled + Disconnect Done Enable Enabled @@ -96,11 +107,13 @@ OK Primary Card Passphrase + Paste Read more Receive Reject Reload Rename + Retry Save changes Search Search tokens @@ -124,6 +137,7 @@ I understand There was an error. Please try again. Unreachable + Warning Yes Contract address copied! Available networks @@ -170,6 +184,7 @@ Flip-to-Hide Balances Issuer Signed + If you forget the code you will lose access to your funds. Code recovery is not possible. Details Check your internet connection or switch to a different network Terms of Service @@ -420,13 +435,13 @@ Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Get your card ready! Already included in the entered address - Subtract - Not enough funds to cover the network fee. Do you want to subtract the amount required to cover the fee? The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? Reason: %1$s\nCode: %2$s The transaction is not completed Amount + Base fee + Represents the part of the transaction fee that goes to the miner %1$s, %2$s Address Destination Tag @@ -474,6 +489,8 @@ Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount. Transaction limitation Optional + Priority fee + Represents the minimum gasUsed multiplier required for a transaction to be included in a block. This is the part of the transaction fee that is burnt. Please align your QR code with the square to scan it. Ensure you scan %s network address. Recent Recipient @@ -540,6 +557,7 @@ This operation is currently unavailable. Please try again later. %s swap is not available. But we are working on adding it. Sell of the %s coin is currently unavailable. But we are working on adding it. + Choose address Generate XPUB Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. @@ -649,6 +667,10 @@ Please change the amount to swap This card might be a production sample or counterfeit Authenticity check failed + Associate + This token must be associated with your Hedera account before you can receive it. Association fee ~%.4f %s + This token must be associated with your Hedera account before you can receive it + Associate your token Only %s signatures are left on this card. You must withdraw all of your funds. Low signature count Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt index 712fc8cb52..f3cbfca76d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt @@ -47,14 +47,15 @@ inline fun SegmentedButtons( selectedColor: Color = TangemTheme.colors.background.action, dividerColor: Color = TangemTheme.colors.stroke.primary, showIndication: Boolean = true, - selectedItem: T? = null, + initialSelectedItem: T? = null, + isEnabled: Boolean = true, crossinline buttonContent: @Composable (T) -> Unit, ) { if (config.isEmpty() || config.size == 1) return - var selected by remember { - val selectedIndex = if (selectedItem == null) 0 else config.indexOf(selectedItem) - mutableIntStateOf(selectedIndex) + var selectedIndex by remember { + val index = if (initialSelectedItem == null) 0 else config.indexOf(initialSelectedItem) + mutableIntStateOf(index) } Row( @@ -69,7 +70,7 @@ inline fun SegmentedButtons( val rightRadius = if (index == config.lastIndex) TangemTheme.dimens.radius26 else TangemTheme.dimens.radius0 val animateColor by animateColorAsState( - targetValue = if (index == selected) selectedColor else color, + targetValue = if (index == selectedIndex) selectedColor else color, label = "Segmented Button Selected Color Animation", animationSpec = spring(stiffness = Spring.StiffnessMedium), ) @@ -86,11 +87,12 @@ inline fun SegmentedButtons( ), ) .clickable( + enabled = isEnabled, interactionSource = remember { MutableInteractionSource() }, indication = if (showIndication) LocalIndication.current else null, ) { + selectedIndex = index onClick(config[index]) - selected = index }, ) { buttonContent.invoke(config[index]) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt index 3255b50af7..76d5c9d094 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt @@ -4,11 +4,16 @@ import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Dp import com.tangem.core.ui.R import com.tangem.core.ui.components.currency.DefaultCurrencyIcon +private const val GRAY_SCALE_SATURATION = 0f +private const val GRAY_SCALE_ALPHA = 0.4f + /** * Simple icon from network * @@ -20,16 +25,19 @@ import com.tangem.core.ui.components.currency.DefaultCurrencyIcon fun FiatIcon( url: String?, size: Dp, + isGrayscale: Boolean, modifier: Modifier = Modifier, @DrawableRes fallbackResId: Int = R.drawable.ic_shape_circle, ) { val iconData: Any = if (url.isNullOrBlank()) fallbackResId else url + val alpha = if (isGrayscale) GRAY_SCALE_ALPHA else 1f + val colorFilter = if (isGrayscale) GrayscaleColorFilter else null DefaultCurrencyIcon( iconData = iconData, size = size, - alpha = 1f, - colorFilter = null, + alpha = alpha, + colorFilter = colorFilter, errorIcon = { Image( painter = painterResource(id = fallbackResId), @@ -38,4 +46,7 @@ fun FiatIcon( }, modifier = modifier, ) -} \ No newline at end of file +} + +private val GrayscaleColorFilter: ColorFilter + get() = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(GRAY_SCALE_SATURATION) }) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt index 19703e82b9..fbcc6e3caa 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt @@ -20,6 +20,26 @@ class CryptoCurrencyToIconStateConverter : Converter getIconStateForCoin( + coin = currency, + isUnreachable = value.value.isError, + forceGrayscale = forceGrayscale, + ) + is CryptoCurrency.Token -> getIconStateForToken( + token = currency, + isErrorStatus = value.value.isError, + forceGrayscale = forceGrayscale, + showCustomBadge = showCustomTokenBadge, + ) + } + } + fun convert(currency: CryptoCurrency): TokenIconState { return when (currency) { is CryptoCurrency.Coin -> getIconStateForCoin(currency, isUnreachable = false) @@ -27,18 +47,27 @@ class CryptoCurrencyToIconStateConverter : Converter Unit, modifi modifier = modifier, ) { Text( - text = "Paste", + text = stringResource(R.string.common_paste), style = TangemTheme.typography.button, color = TangemTheme.colors.text.primary2, modifier = Modifier diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt index 2adcbdc369..6f23701fea 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.utils.toDateFormat +import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.core.ui.utils.toTimeFormat /** @@ -47,7 +47,11 @@ fun TransactionDoneTitle(@StringRes titleRes: Int, date: Long, modifier: Modifie .padding(top = TangemTheme.dimens.spacing16), ) Text( - text = stringResource(id = R.string.send_date_format, date.toDateFormat(), date.toTimeFormat()), + text = stringResource( + id = R.string.send_date_format, + date.toTimeFormat(DateTimeFormatters.dateFormatter), + date.toTimeFormat(), + ), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, modifier = Modifier diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt index 42309223b1..b724559114 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt @@ -40,6 +40,29 @@ object BigDecimalFormatter { } } + fun formatCryptoAmountUncapped( + cryptoAmount: BigDecimal?, + cryptoCurrency: CryptoCurrency, + locale: Locale = Locale.getDefault(), + ): String { + if (cryptoAmount == null) return EMPTY_BALANCE_SIGN + + val formatter = NumberFormat.getNumberInstance(locale).apply { + maximumFractionDigits = cryptoCurrency.decimals + minimumFractionDigits = 2 + isGroupingUsed = true + roundingMode = RoundingMode.DOWN + } + + return formatter.format(cryptoAmount).let { + if (cryptoCurrency.symbol.isEmpty()) { + it + } else { + it + "\u2009${cryptoCurrency.symbol}" + } + } + } + fun formatCryptoAmount( cryptoAmount: BigDecimal?, cryptoCurrency: CryptoCurrency, diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt index 8d596fc41f..bc2560f6e9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt @@ -11,7 +11,7 @@ import org.joda.time.format.DateTimeFormatter * If [this] timestamp is today or yesterday, returns relative date, * otherwise returns formatting date. */ -fun Long.toDateFormat(formatter: DateTimeFormatter = DateTimeFormatters.dateFormatter): String { +fun Long.toDateFormatWithTodayYesterday(formatter: DateTimeFormatter = DateTimeFormatters.dateFormatter): String { val localDate = DateTime(this, DateTimeZone.getDefault()) return if (localDate.isToday() || localDate.isYesterday()) { DateUtils.getRelativeTimeSpanString( diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt index cce12822c5..c4677126a6 100644 --- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt +++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt @@ -6,9 +6,9 @@ import com.tangem.domain.qrscanning.models.QrResult import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.repository.QrScanningEventsRepository import com.tangem.domain.tokens.model.CryptoCurrency -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.yield import java.math.BigDecimal import java.net.URLDecoder @@ -16,15 +16,20 @@ internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository { private data class QrScanningEvent(val type: SourceType, val qrCode: String) - private val scannedEvents = MutableSharedFlow() + private val scannedEvents = MutableSharedFlow(replay = 1) override suspend fun emitResult(type: SourceType, qrCode: String) { scannedEvents.emit(QrScanningEvent(type, qrCode)) } + @OptIn(ExperimentalCoroutinesApi::class) override fun subscribeToScanningResults(type: SourceType) = scannedEvents .filter { it.type == type } .map { it.qrCode } + .onEach { + yield() // if we have more than one sub, we must allow them to collect emitted value + scannedEvents.resetReplayCache() + } override fun parseQrCode(qrCode: String, cryptoCurrency: CryptoCurrency): QrResult { val withoutSchema = stripSchema(qrCode, cryptoCurrency) diff --git a/domain/demo/build.gradle.kts b/domain/demo/build.gradle.kts index f0c56e2a73..a9e6de5249 100644 --- a/domain/demo/build.gradle.kts +++ b/domain/demo/build.gradle.kts @@ -10,4 +10,5 @@ android { dependencies { implementation(deps.tangem.blockchain) + implementation(deps.tangem.card.core) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoTransactionSender.kt b/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt similarity index 98% rename from app/src/main/java/com/tangem/tap/features/demo/DemoTransactionSender.kt rename to domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt index faecf5aac1..e98373f775 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoTransactionSender.kt +++ b/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.features.demo +package com.tangem.domain.demo import com.tangem.blockchain.common.* import com.tangem.blockchain.common.transaction.Fee diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt index 87015b1305..db909d08f3 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt @@ -184,8 +184,9 @@ sealed class CryptoCurrency : Parcelable { } private companion object { + // should use delimiters that could be used in URL not like path or query delimiters const val PREFIX_DELIMITER = '_' - const val SUFFIX_DELIMITER = '#' + const val SUFFIX_DELIMITER = ';' const val DERIVATION_PATH_DELIMITER = 'd' } } diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt index 22b7991290..3f5f7485d4 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt @@ -4,7 +4,7 @@ import com.tangem.core.ui.extensions.TextReference sealed class SendTransactionError { - object DemoCardError : SendTransactionError() + data object DemoCardError : SendTransactionError() data class DataError(val message: String?) : SendTransactionError() @@ -12,7 +12,7 @@ sealed class SendTransactionError { data class BlockchainSdkError(val code: Int, val message: String?) : SendTransactionError() - object UserCancelledError : SendTransactionError() + data object UserCancelledError : SendTransactionError() data class CreateAccountUnderfunded(val amount: String) : SendTransactionError() diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt index ae697ceb08..2f113a02a0 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt @@ -6,10 +6,12 @@ import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Token import com.tangem.blockchain.extensions.Result +import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.demo.DemoTransactionSender import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWallet import java.math.BigDecimal /** @@ -17,23 +19,31 @@ import java.math.BigDecimal */ class GetFeeUseCase( private val walletManagersFacade: WalletManagersFacade, + private val demoConfig: DemoConfig, ) { suspend operator fun invoke( amount: BigDecimal, destination: String, - userWalletId: UserWalletId, + userWallet: UserWallet, cryptoCurrency: CryptoCurrency, ) = either { catch( block = { - val result = requireNotNull( - walletManagersFacade.getFee( - amount = convertCryptoCurrencyToAmount(cryptoCurrency, amount), + val amountData = convertCryptoCurrencyToAmount(cryptoCurrency, amount) + + val result = if (demoConfig.isDemoCardId(userWallet.scanResponse.card.cardId)) { + demoTransactionSender(userWallet, cryptoCurrency).getFee( + amount = amountData, destination = destination, - userWalletId = userWalletId, + ) + } else { + walletManagersFacade.getFee( + amount = amountData, + destination = destination, + userWalletId = userWallet.walletId, network = cryptoCurrency.network, - ), - ) { "Fee is null" } + ) ?: error("Fee is null") + } val maybeFee = when (result) { is Result.Success -> result.data @@ -47,6 +57,17 @@ class GetFeeUseCase( ) } + private suspend fun demoTransactionSender( + userWallet: UserWallet, + cryptoCurrency: CryptoCurrency, + ): DemoTransactionSender { + return DemoTransactionSender( + walletManagersFacade + .getOrCreateWalletManager(userWallet.walletId, cryptoCurrency.network) + ?: error("WalletManager is null"), + ) + } + private fun convertCryptoCurrencyToAmount(cryptoCurrency: CryptoCurrency, amount: BigDecimal) = Amount( currencySymbol = cryptoCurrency.symbol, value = amount, diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt index d2c9cbd085..379d90d4be 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt @@ -5,6 +5,7 @@ import arrow.core.left import arrow.core.right import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.extensions.SimpleResult import com.tangem.blockchain.network.ResultChecker import com.tangem.common.core.TangemSdkError @@ -12,20 +13,23 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.common.TapWorkarounds.isStart2Coin -import com.tangem.domain.demo.IsDemoCardUseCase +import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.demo.DemoTransactionSender import com.tangem.domain.tokens.model.Network import com.tangem.domain.transaction.R import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.domain.transaction.error.SendTransactionError.Companion.USER_CANCELLED_ERROR_CODE +import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWallet import com.tangem.sdk.extensions.localizedDescriptionRes import com.tangem.utils.toFormattedString class SendTransactionUseCase( - private val isDemoCardUseCase: IsDemoCardUseCase, + private val demoConfig: DemoConfig, private val cardSdkConfigRepository: CardSdkConfigRepository, private val transactionRepository: TransactionRepository, + private val walletManagersFacade: WalletManagersFacade, ) { suspend operator fun invoke( txData: TransactionData, @@ -39,8 +43,13 @@ class SendTransactionUseCase( cardSdkConfigRepository.setLinkedTerminal(false) } val sendResult = try { - if (isDemoCardUseCase(cardId = userWallet.cardId)) { - SendTransactionError.DemoCardError.left() + if (demoConfig.isDemoCardId(cardId = userWallet.cardId)) { + sendDemo( + userWallet = userWallet, + network = network, + transactionData = txData, + signer = signer, + ) } else { transactionRepository.sendTransaction( txData = txData, @@ -66,6 +75,27 @@ class SendTransactionUseCase( ) } + private suspend fun sendDemo( + userWallet: UserWallet, + network: Network, + transactionData: TransactionData, + signer: TransactionSigner, + ): Either { + val demoTransactionSender = DemoTransactionSender( + walletManagersFacade + .getOrCreateWalletManager(userWallet.walletId, network) + ?: error("WalletManager is null"), + ) + + val result = demoTransactionSender.send(transactionData = transactionData, signer = signer) + + return if (result is SimpleResult.Failure && result.error.customMessage.contains(DemoTransactionSender.ID)) { + SendTransactionError.DemoCardError.left() + } else { + result.right() + } + } + private fun handleError(result: SimpleResult.Failure): SendTransactionError { if (ResultChecker.isNetworkError(result)) { return SendTransactionError.NetworkError( diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt index 94f072a0ac..609e2df784 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt @@ -93,7 +93,7 @@ private fun SegmentSeedBlock(state: SegmentSeedState, modifier: Modifier = Modif SegmentedButtons( modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing76), config = state.seedSegments, - selectedItem = state.selectedSeedType, + initialSelectedItem = state.selectedSeedType, onClick = { state.onSelectType(it) }, diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt index 577380dc6b..f232473da8 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt @@ -4,6 +4,7 @@ import android.Manifest import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle +import android.view.View import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -15,6 +16,8 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.mlkit.vision.common.InputImage import com.tangem.core.ui.res.TangemTheme @@ -25,6 +28,8 @@ import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter import com.tangem.feature.qrscanning.presentation.QrScanningContent import com.tangem.feature.qrscanning.viewmodel.QrScanningViewModel import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import javax.inject.Inject @@ -69,11 +74,24 @@ internal class QrScanningFragment : ComposeFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewModel.setRouter(innerRouter, galleryLauncher) + viewModel.setRouter(innerRouter) cameraExecutor = Executors.newSingleThreadExecutor() requestCameraPermission() } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.launchGalleryEvent + .collect { + galleryLauncher.launch(it.imageFilter) + delay(timeMillis = 2000) + } + } + } + } + override fun onResume() { super.onResume() checkPermissionGranted() diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt index a9c37e465c..2ff968422a 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt @@ -1,6 +1,5 @@ package com.tangem.feature.qrscanning.viewmodel -import androidx.activity.result.ActivityResultLauncher import com.tangem.domain.qrscanning.models.SourceType import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter import kotlinx.coroutines.CoroutineScope @@ -11,23 +10,14 @@ internal open class BaseQrScanningClickIntents { protected val router: QrScanningInnerRouter get() = _router protected val viewModelScope: CoroutineScope get() = _viewModelScope protected val source: SourceType get() = _source - protected val galleryLauncher: ActivityResultLauncher get() = _galleryLauncher private var _router: QrScanningInnerRouter by Delegates.notNull() private var _viewModelScope: CoroutineScope by Delegates.notNull() private var _source: SourceType by Delegates.notNull() - private var _galleryLauncher: ActivityResultLauncher by Delegates.notNull() - - open fun initialize( - router: QrScanningInnerRouter, - source: SourceType, - galleryLauncher: ActivityResultLauncher, - coroutineScope: CoroutineScope, - ) { + open fun initialize(router: QrScanningInnerRouter, source: SourceType, coroutineScope: CoroutineScope) { _router = router _viewModelScope = coroutineScope _source = source - _galleryLauncher = galleryLauncher } } \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt index 64d837b7f4..075894e1c6 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt @@ -5,11 +5,16 @@ import com.tangem.feature.qrscanning.presentation.QrScanningStateController import com.tangem.feature.qrscanning.presentation.transformers.DismissBottomSheetTransformer import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch import javax.inject.Inject interface QrScanningClickIntents { + val launchGallery: SharedFlow + fun onBackClick() fun onQrScanned(qrCode: String) @@ -17,6 +22,11 @@ interface QrScanningClickIntents { fun onGalleryClicked() } +@JvmInline +value class GalleryRequest( + val imageFilter: String, +) + @ViewModelScoped internal class QrScanningClickIntentsImplementor @Inject constructor( private val stateHolder: QrScanningStateController, @@ -26,6 +36,11 @@ internal class QrScanningClickIntentsImplementor @Inject constructor( private var isScanned = false + override val launchGallery = MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_LATEST, + ) + override fun onBackClick() = router.popBackStack() override fun onQrScanned(qrCode: String) { @@ -41,7 +56,7 @@ internal class QrScanningClickIntentsImplementor @Inject constructor( } override fun onGalleryClicked() { - galleryLauncher.launch(GALLERY_IMAGE_FILTER) + launchGallery.tryEmit(GalleryRequest(imageFilter = GALLERY_IMAGE_FILTER)) if (stateHolder.value.bottomSheetConfig != null) { stateHolder.update(DismissBottomSheetTransformer()) } diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt index 4320634f4d..cca4825f93 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt @@ -1,6 +1,5 @@ package com.tangem.feature.qrscanning.viewmodel -import androidx.activity.result.ActivityResultLauncher import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -14,6 +13,7 @@ import com.tangem.feature.qrscanning.presentation.transformers.DismissBottomShee import com.tangem.feature.qrscanning.presentation.transformers.InitializeQrScanningStateTransformer import com.tangem.feature.qrscanning.presentation.transformers.ShowCameraDeniedBottomSheetTransformer import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @@ -28,12 +28,12 @@ internal class QrScanningViewModel @Inject constructor( private val network: String? = savedStateHandle[NETWORK_KEY] val uiState: StateFlow = stateHolder.uiState + val launchGalleryEvent: SharedFlow = clickIntents.launchGallery - fun setRouter(router: QrScanningInnerRouter, galleryLauncher: ActivityResultLauncher) { + fun setRouter(router: QrScanningInnerRouter) { clickIntents.initialize( router = router, source = source, - galleryLauncher = galleryLauncher, coroutineScope = viewModelScope, ) stateHolder.update(InitializeQrScanningStateTransformer(clickIntents, source, network)) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 279154964b..74f9653422 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -27,7 +27,6 @@ import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.components.appbar.AppBarWithBackButton -import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.referral.domain.models.ExpectedAward import com.tangem.feature.referral.domain.models.ExpectedAwards @@ -350,7 +349,7 @@ private fun BoxScope.ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) { modifier = Modifier.fillMaxWidth(), actionOnNewLine = true, shape = RoundedCornerShape(size = TangemTheme.dimens.radius8), - containerColor = TangemColorPalette.Black, + containerColor = TangemTheme.colors.button.primary, contentColor = TangemTheme.colors.text.primary2, actionColor = TangemTheme.colors.text.primary2, ) @@ -464,7 +463,7 @@ private fun Preview_ReferralScreen_Participant_InLightTheme() { url = "", expectedAwards = null, ), - errorSnackbar = null, + errorSnackbar = ErrorSnackbar(DemoModeException()) {}, analytics = Analytics( onAgreementClicked = {}, onCopyClicked = {}, @@ -493,7 +492,7 @@ private fun Preview_ReferralScreen_Participant_InDarkTheme() { url = "", expectedAwards = null, ), - errorSnackbar = null, + errorSnackbar = ErrorSnackbar(DemoModeException()) {}, analytics = Analytics( onAgreementClicked = {}, onCopyClicked = {}, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt index 2f48bcb034..e99471d8de 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt @@ -103,6 +103,10 @@ internal sealed class SendAnalyticEvents( params = mapOf(TOKEN to token), ) + data object NoticeFeeCoverage : SendAnalyticEvents( + event = "Notice - Network Fee Coverage", + ) + /** If error occurs during send transactions */ data class TransactionError(val token: String) : SendAnalyticEvents( event = "Error - Transaction Rejected", diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt index 6de841373c..fc7ae7daf1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt @@ -68,16 +68,6 @@ internal sealed class SendAlertState { override val confirmButtonText: TextReference = resourceReference(R.string.common_continue) } - data class FeeCoverage( - override val onConfirmClick: (() -> Unit), - ) : SendAlertState() { - override val title: TextReference? = null - override val message: TextReference = - resourceReference(id = R.string.send_alert_fee_coverage_title) - override val confirmButtonText: TextReference = - resourceReference(id = R.string.send_alert_fee_coverage_subract_text) - } - data class ReserveAmount(val amount: String) : SendAlertState() { override val title: TextReference = resourceReference(id = R.string.send_notification_invalid_reserve_amount_title, wrappedList(amount)) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt index d99b6bd0ff..616fad69f8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt @@ -48,19 +48,6 @@ internal class SendEventStateFactory( ) } - fun getFeeCoverageAlert(onConsume: () -> Unit): SendUiState { - return currentStateProvider().copy( - event = triggeredEvent( - data = SendEvent.ShowAlert( - SendAlertState.FeeCoverage( - onConfirmClick = clickIntents::onSubtractSelect, - ), - ), - onConsume = onConsume, - ), - ) - } - fun getFeeUpdatedAlert(fee: TransactionFee, onConsume: () -> Unit, onFeeNotIncreased: () -> Unit): SendUiState { val state = currentStateProvider() val feeSelector = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return state @@ -139,9 +126,7 @@ internal class SendEventStateFactory( return state.copy( event = triggeredEvent( data = SendEvent.ShowAlert( - SendAlertState.FeeUnreachableError( - onConfirmClick = { clickIntents.feeReload(true) }, - ), + SendAlertState.FeeUnreachableError(onConfirmClick = clickIntents::feeReload), ), onConsume = onConsume, ), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt index e59bd0e517..3334f3faaa 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt @@ -42,11 +42,6 @@ internal sealed class SendNotification(val config: NotificationConfig) { ), ) - data class ReserveAmountError(val amount: String) : Error( - title = resourceReference(R.string.send_notification_invalid_reserve_amount_title, wrappedList(amount)), - subtitle = resourceReference(R.string.send_notification_invalid_reserve_amount_text), - ) - data class TransactionLimitError( val cryptoCurrency: String, val utxoLimit: String, @@ -153,5 +148,10 @@ internal sealed class SendNotification(val config: NotificationConfig) { onClick = onRefresh, ), ) + + data object FeeCoverageNotification : Warning( + title = resourceReference(R.string.send_network_fee_warning_title), + subtitle = resourceReference(R.string.swapping_network_fee_warning_content), + ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index f3f304b7ff..b31e9559be 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -14,7 +14,6 @@ import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter -import com.tangem.features.send.impl.presentation.state.amount.SendAmountSubtractConverter import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter @@ -48,12 +47,6 @@ internal class SendStateFactory( appCurrencyProvider = appCurrencyProvider, ) } - private val amountSubtractConverter by lazy(LazyThreadSafetyMode.NONE) { - SendAmountSubtractConverter( - currentStateProvider = currentStateProvider, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - ) - } private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) { SendAmountStateConverter( appCurrencyProvider = appCurrencyProvider, @@ -117,6 +110,7 @@ internal class SendStateFactory( recipientState = state.recipientState ?: recipientStateConverter.convert(SendRecipientStateConverter.Data(destinationAddress, memo)), feeState = state.feeState ?: feeStateConverter.convert(Unit), + sendState = confirmStateConverter.convert(Unit), isEditingDisabled = true, cryptoCurrencyName = cryptoCurrencyStatusProvider().currency.name, ) @@ -264,14 +258,6 @@ internal class SendStateFactory( //endregion //region send - fun onSubtractSelect(isAmountSubtractAvailable: Boolean): SendUiState { - val state = currentStateProvider() - - if (!isAmountSubtractAvailable) return state - - return amountSubtractConverter.convert(Unit) - } - fun getSendingStateUpdate(isSending: Boolean): SendUiState { val state = currentStateProvider() return state.copy( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt index 586c55c978..2b14283039 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt @@ -48,11 +48,9 @@ internal sealed class SendStates { val walletBalance: TextReference, val tokenIconState: TokenIconState, val segmentedButtonConfig: PersistentList, + val isSegmentedButtonsEnabled: Boolean, val amountTextField: SendTextField.AmountField, - val notifications: ImmutableList, val appCurrencyCode: String, - val isFeeLoading: Boolean, - val subtractedFee: BigDecimal?, ) : SendStates() /** Recipient state */ @@ -89,7 +87,6 @@ internal sealed class SendStates { override val isPrimaryButtonEnabled: Boolean = true, val isSending: Boolean, val isSuccess: Boolean, - val isSubtract: Boolean, val transactionDate: Long, val txUrl: String, val ignoreAmountReduce: Boolean, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt index 4577927cc1..6f56b6aff3 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt @@ -1,12 +1,10 @@ package com.tangem.features.send.impl.presentation.state.amount import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.presentation.state.SendNotification import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldMaxAmountConverter import com.tangem.utils.Provider -import kotlinx.collections.immutable.ImmutableList /** * Factory to produce amount state for [SendUiState] @@ -43,18 +41,4 @@ internal class AmountStateFactory( } fun getOnCurrencyChangedState(isFiat: Boolean) = amountCurrencyConverter.convert(isFiat) - - fun getAmountNotificationState(notifications: ImmutableList): SendUiState { - val state = currentStateProvider() - return state.copy( - amountState = state.amountState?.copy(notifications = notifications), - ) - } - - fun getOnAmountFeeLoadingCancel(): SendUiState { - val state = currentStateProvider() - return state.copy( - amountState = state.amountState?.copy(isFeeLoading = false), - ) - } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt index f06a20bf48..7e3199d1ff 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt @@ -31,6 +31,7 @@ internal class SendAmountStateConverter( val status = cryptoCurrencyStatusProvider() val fiat = formatFiatAmount(status.value.fiatAmount, appCurrency.code, appCurrency.symbol) val crypto = formatCryptoAmount(status.value.amount, status.currency.symbol, status.currency.decimals) + val noFeeRate = status.value.fiatRate.isNullOrZero() return SendStates.AmountState( walletName = userWallet.name, @@ -38,26 +39,24 @@ internal class SendAmountStateConverter( tokenIconState = iconStateConverter.convert(status), amountTextField = sendAmountFieldConverter.convert(value), isPrimaryButtonEnabled = false, - notifications = persistentListOf(), - isFeeLoading = false, appCurrencyCode = appCurrency.code, - subtractedFee = null, - segmentedButtonConfig = if (status.value.fiatRate.isNullOrZero()) { - persistentListOf() - } else { - persistentListOf( - SendAmountSegmentedButtonsConfig( - title = stringReference(status.currency.symbol), - iconState = iconStateConverter.convert(status), - isFiat = false, + segmentedButtonConfig = persistentListOf( + SendAmountSegmentedButtonsConfig( + title = stringReference(status.currency.symbol), + iconState = iconStateConverter.convertCustom( + value = status, + forceGrayscale = noFeeRate, + showCustomTokenBadge = false, ), - SendAmountSegmentedButtonsConfig( - title = stringReference(appCurrency.code), - iconUrl = appCurrency.iconSmallUrl, - isFiat = true, - ), - ) - }, + isFiat = false, + ), + SendAmountSegmentedButtonsConfig( + title = stringReference(appCurrency.code), + iconUrl = appCurrency.iconSmallUrl, + isFiat = true, + ), + ), + isSegmentedButtonsEnabled = !noFeeRate, ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt deleted file mode 100644 index bf0f52dd73..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.amount - -import com.tangem.core.ui.utils.parseBigDecimal -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import java.math.BigDecimal - -internal class SendAmountSubtractConverter( - private val currentStateProvider: Provider, - private val cryptoCurrencyStatusProvider: Provider, -) : Converter { - override fun convert(value: Unit): SendUiState { - val state = currentStateProvider() - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val amountState = state.amountState ?: return state - val feeState = state.feeState ?: return state - val feeValue = feeState.fee?.amount?.value ?: return state - val amountTextField = amountState.amountTextField - val amountValue = amountTextField.cryptoAmount.value ?: return state - val fiatRate = cryptoCurrencyStatus.value.fiatRate - val cryptoDecimals = amountTextField.cryptoAmount.decimals - val fiatDecimals = amountTextField.fiatAmount.decimals - - val feeDiff = amountState.subtractedFee?.let { feeValue.minus(it) } ?: feeValue - val decimalCryptoValue = amountValue.minus(feeDiff) - - if (decimalCryptoValue < BigDecimal.ZERO) return state - - val decimalFiatValue = decimalCryptoValue.multiply(fiatRate) - val cryptoValue = decimalCryptoValue.parseBigDecimal(cryptoDecimals) - val fiatValue = decimalFiatValue.parseBigDecimal(fiatDecimals) - - return state.copy( - sendState = state.sendState?.copy(isSubtract = true), - amountState = amountState.copy( - subtractedFee = feeValue, - amountTextField = amountTextField.copy( - value = cryptoValue, - fiatValue = fiatValue, - cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), - fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), - ), - ), - ) - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt index d5662a10b7..e202bf5e25 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt @@ -13,7 +13,6 @@ internal class SendConfirmStateConverter( isPrimaryButtonEnabled = true, isSending = false, isSuccess = false, - isSubtract = false, transactionDate = 0L, txUrl = "", ignoreAmountReduce = false, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index e71cdf4d14..563ec2af7f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -18,12 +18,11 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents import com.tangem.features.send.impl.presentation.state.* -import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState -import com.tangem.features.send.impl.presentation.state.fee.FeeType +import com.tangem.features.send.impl.presentation.state.fee.* import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.lib.crypto.BlockchainUtils.isDogecoin +import com.tangem.lib.crypto.BlockchainUtils.isTezos import com.tangem.utils.Provider -import com.tangem.utils.isNullOrZero import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -31,17 +30,16 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import java.math.BigDecimal -import java.math.BigInteger @Suppress("LongParameterList") internal class SendNotificationFactory( private val cryptoCurrencyStatusProvider: Provider, private val coinCryptoCurrencyStatusProvider: Provider, - private val feePaidCryptoCurrencyStatusProvider: Provider, private val currentStateProvider: Provider, private val userWalletProvider: Provider, private val currencyChecksRepository: CurrencyChecksRepository, private val stateRouterProvider: Provider, + private val isSubtractAvailableProvider: Provider, private val clickIntents: SendClickIntents, private val analyticsEventHandler: AnalyticsEventHandler, private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, @@ -53,24 +51,39 @@ internal class SendNotificationFactory( val state = currentStateProvider() val sendState = state.sendState ?: return@map persistentListOf() val feeState = state.feeState ?: return@map persistentListOf() - val feeAmount = feeState.fee?.amount?.value ?: BigDecimal.ZERO + val balance = cryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO + val feeValue = feeState.fee?.amount?.value ?: BigDecimal.ZERO + val isFeeCoverage = checkFeeCoverage( + isSubtractAvailable = isSubtractAvailableProvider(), + balance = balance, + amountValue = amountValue, + feeValue = feeValue, + ) + val sendingAmount = calculateSubtractedAmount( + isFeeCoverage = isFeeCoverage, + cryptoCurrencyStatus = cryptoCurrencyStatusProvider(), + amountValue = amountValue, + feeValue = feeValue, + ) buildList { // errors - addExceedBalanceNotification(feeAmount, amountValue) + addFeeUnreachableNotification(feeState.feeSelectorState) + addExceedBalanceNotification(feeValue, sendingAmount) addExceedsBalanceNotification(feeState.fee) - addMinimumAmountErrorNotification(feeAmount, amountValue) - addDustWarningNotification(feeAmount, amountValue) - addTransactionLimitErrorNotification(feeAmount, amountValue) + addMinimumAmountErrorNotification(feeValue, sendingAmount) + addDustWarningNotification(feeValue, sendingAmount) + addTransactionLimitErrorNotification(feeValue, sendingAmount) // warnings - addExistentialWarningNotification(feeAmount, amountValue) - addHighFeeWarningNotification(feeAmount, amountValue, sendState.ignoreAmountReduce) + addExistentialWarningNotification(feeValue, amountValue) + addFeeCoverageNotification(isFeeCoverage) + addHighFeeWarningNotification(amountValue, sendState.ignoreAmountReduce) addTooHighNotification(feeState.feeSelectorState) addTooLowNotification(feeState) }.toImmutableList() } - fun dismissNotificationState(clazz: Class): SendUiState { + fun dismissNotificationState(clazz: Class, isIgnored: Boolean = false): SendUiState { val state = currentStateProvider() val sendState = state.sendState ?: return state val notificationsToRemove = sendState.notifications.filterIsInstance(clazz) @@ -78,50 +91,33 @@ internal class SendNotificationFactory( updatedNotifications.removeAll(notificationsToRemove) return state.copy( sendState = sendState.copy( - ignoreAmountReduce = true, + ignoreAmountReduce = isIgnored, notifications = updatedNotifications.toImmutableList(), ), ) } + private fun MutableList.addFeeUnreachableNotification(feeSelectorState: FeeSelectorState) { + if (feeSelectorState is FeeSelectorState.Error) { + add(SendNotification.Warning.NetworkFeeUnreachable(clickIntents::feeReload)) + } + } + private fun MutableList.addExceedBalanceNotification( feeAmount: BigDecimal, receivedAmount: BigDecimal, ) { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider() - val feePaidCryptoCurrencyStatus = feePaidCryptoCurrencyStatusProvider() - val cryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val coinCryptoAmount = coinCryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val showNotification = if (cryptoCurrencyStatus.currency.id == feePaidCryptoCurrencyStatus?.currency?.id) { - receivedAmount > cryptoAmount || feeAmount > coinCryptoAmount - } else { - receivedAmount + feeAmount > cryptoAmount - } + if (!isSubtractAvailableProvider()) return + val showNotification = receivedAmount + feeAmount > balance if (showNotification) { add(SendNotification.Error.TotalExceedsBalance) } } - private fun MutableList.addMinimumAmountErrorNotification( - feeAmount: BigDecimal, - receivedAmount: BigDecimal, - ) { - val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider() - - val totalAmount = feeAmount + receivedAmount - val balance = coinCryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - - if (isDogecoin(coinCryptoCurrencyStatus.currency.network.id.value)) { - val minimum = BigDecimal(DOGECOIN_MINIMUM) - if (receivedAmount < minimum || balance - totalAmount < minimum) { - add(SendNotification.Error.MinimumAmountError(DOGECOIN_MINIMUM)) - } - } - } - // todo temporarily disabling notification // private suspend fun MutableList.addReserveAmountErrorNotification(recipientAddress: String) { // val userWalletId = userWalletProvider().walletId @@ -198,7 +194,7 @@ internal class SendNotificationFactory( if (currencyDeposit != null && currencyDeposit > diff) { add( SendNotification.Error.ExistentialDeposit( - BigDecimalFormatter.formatCryptoAmount( + BigDecimalFormatter.formatCryptoAmountUncapped( cryptoAmount = currencyDeposit, cryptoCurrency = cryptoCurrency, ), @@ -207,16 +203,22 @@ internal class SendNotificationFactory( } } + private fun MutableList.addFeeCoverageNotification(sendingAmount: Boolean) { + if (sendingAmount) { + analyticsEventHandler.send(SendAnalyticEvents.NoticeFeeCoverage) + add(SendNotification.Warning.FeeCoverageNotification) + } + } + private fun MutableList.addHighFeeWarningNotification( - feeAmount: BigDecimal, sendAmount: BigDecimal, ignoreAmountReduce: Boolean, ) { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val isTezos = cryptoCurrencyStatus.currency.network.id.value == Blockchain.Tezos.id + val isTezos = isTezos(cryptoCurrencyStatus.currency.network.id.value) val threshold = Blockchain.Tezos.minimalAmount() - val isTotalBalance = feeAmount.plus(sendAmount) >= balance && balance > threshold + val isTotalBalance = sendAmount >= balance && balance > threshold if (!ignoreAmountReduce && isTotalBalance && isTezos) { add( SendNotification.Warning.HighFeeError( @@ -233,6 +235,21 @@ internal class SendNotificationFactory( } } + // todo remove in [REDACTED_TASK_KEY] + private fun MutableList.addMinimumAmountErrorNotification( + feeAmount: BigDecimal, + receivedAmount: BigDecimal, + ) { + val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider() + val minimum = BigDecimal(DOGECOIN_MINIMUM) + + val isDogecoin = isDogecoin(coinCryptoCurrencyStatus.currency.network.id.value) + val isExceedDustLimit = checkDustLimits(feeAmount, receivedAmount, minimum) + if (isDogecoin && isExceedDustLimit) { + add(SendNotification.Error.MinimumAmountError(DOGECOIN_MINIMUM)) + } + } + private suspend fun MutableList.addDustWarningNotification( feeAmount: BigDecimal, receivedAmount: BigDecimal, @@ -241,18 +258,12 @@ internal class SendNotificationFactory( val dustValue = currencyChecksRepository.getDustValue( userWalletProvider().walletId, cryptoCurrencyStatus.currency.network, - ) - val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - if (dustValue != null && !balance.isNullOrZero() && receivedAmount < balance) { - val totalAmount = feeAmount + receivedAmount - val change = balance - totalAmount - val isChangeLowerThanDust = change < dustValue && change != BigDecimal.ZERO - val isShowWarning = totalAmount < dustValue || isChangeLowerThanDust - if (isShowWarning) { - add( - SendNotification.Error.MinimumAmountError(dustValue.toPlainString()), - ) - } + ) ?: return + + if (checkDustLimits(feeAmount, receivedAmount, dustValue)) { + add( + SendNotification.Error.MinimumAmountError(dustValue.toPlainString()), + ) } } @@ -274,13 +285,9 @@ internal class SendNotificationFactory( private fun MutableList.addTooHighNotification(feeSelectorState: FeeSelectorState) { if (feeSelectorState !is FeeSelectorState.Content) return - val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return - val highValue = multipleFees.priority.amount.value ?: return - val customAmount = feeSelectorState.customValues.firstOrNull() ?: return - val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) - val diff = (customValue / highValue).toBigInteger() - if (feeSelectorState.selectedFee == FeeType.Custom && diff > FEE_MAX_DIFF) { - add(SendNotification.Warning.TooHigh(diff.toString())) + + checkIfFeeTooHigh(feeSelectorState) { diff -> + add(SendNotification.Warning.TooHigh(diff)) } } @@ -361,8 +368,17 @@ internal class SendNotificationFactory( return Blockchain.fromNetworkId(this.currency.network.backendId) == Blockchain.Arbitrum } + private fun checkDustLimits(feeAmount: BigDecimal, receivedAmount: BigDecimal, dustValue: BigDecimal): Boolean { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + + val totalAmount = feeAmount + receivedAmount + val change = balance - totalAmount + val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO + return receivedAmount < dustValue || isChangeLowerThanDust + } + companion object { private const val DOGECOIN_MINIMUM = "0.01" - internal val FEE_MAX_DIFF = BigInteger("5") } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt index 57e567a586..5a9062da56 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt @@ -1,19 +1,73 @@ package com.tangem.features.send.impl.presentation.state.fee +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.blockchainsdk.utils.minimalAmount +import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.confirm.SendNotificationFactory +import com.tangem.lib.crypto.BlockchainUtils +import java.math.BigDecimal +import java.math.RoundingMode /** - * Check if sending amount with fee is greater than balance + * Check and calculates subtracted amount */ -internal fun checkFeeCoverage(state: SendUiState, cryptoCurrencyStatus: CryptoCurrencyStatus): Boolean { - val balance = cryptoCurrencyStatus.value.amount ?: return false - val fee = state.feeState?.fee?.amount?.value ?: return false - val amount = state.amountState?.amountTextField?.cryptoAmount?.value ?: return false - return balance < amount + fee && balance > fee +internal fun checkAndCalculateSubtractedAmount( + isAmountSubtractAvailable: Boolean, + cryptoCurrencyStatus: CryptoCurrencyStatus, + amountValue: BigDecimal, + feeValue: BigDecimal, +): BigDecimal { + val balance = cryptoCurrencyStatus.value.amount ?: return amountValue + val isFeeCoverage = checkFeeCoverage( + isSubtractAvailable = isAmountSubtractAvailable, + balance = balance, + amountValue = amountValue, + feeValue = feeValue, + ) + return calculateSubtractedAmount( + isFeeCoverage = isFeeCoverage, + cryptoCurrencyStatus = cryptoCurrencyStatus, + amountValue = amountValue, + feeValue = feeValue, + ) +} + +/** + * Checks if sending amount with fee is greater than balance + */ +internal fun checkFeeCoverage( + isSubtractAvailable: Boolean, + balance: BigDecimal, + amountValue: BigDecimal, + feeValue: BigDecimal, +): Boolean { + if (!isSubtractAvailable) return false + return balance < amountValue + feeValue && balance > feeValue +} + +/** + * Calculates subtracted amount + */ +internal fun calculateSubtractedAmount( + isFeeCoverage: Boolean, + cryptoCurrencyStatus: CryptoCurrencyStatus, + amountValue: BigDecimal, + feeValue: BigDecimal, +): BigDecimal { + val balance = cryptoCurrencyStatus.value.amount ?: return amountValue + return if (isFeeCoverage) { + var subtractedValue = minOf(amountValue, balance.minus(feeValue)) + if (BlockchainUtils.isTezos(cryptoCurrencyStatus.currency.network.id.value)) { + val threshold = Blockchain.Tezos.minimalAmount() + subtractedValue = -threshold + } + subtractedValue + } else { + amountValue + } } /** @@ -32,14 +86,16 @@ internal fun checkIfFeeTooLow(state: SendUiState): Boolean { /** * Check if custom fee is too high */ -internal fun checkIfFeeTooHigh(state: SendUiState, onShow: (String) -> Unit): Boolean { - val feeSelectorState = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false +internal fun checkIfFeeTooHigh(feeSelectorState: FeeSelectorState.Content, onShow: (String) -> Unit): Boolean { val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return false val highValue = multipleFees.priority.amount.value ?: return false val customAmount = feeSelectorState.customValues.firstOrNull() ?: return false val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) - val diff = (customValue / highValue).toBigInteger() - val isShow = feeSelectorState.selectedFee == FeeType.Custom && diff > SendNotificationFactory.FEE_MAX_DIFF - if (isShow) onShow(diff.toString()) + val diff = customValue / highValue + val isShow = feeSelectorState.selectedFee == FeeType.Custom && diff > FEE_MAX_DIFF + if (isShow) onShow(diff.parseBigDecimal(ZERO_DECIMALS, RoundingMode.HALF_UP)) return isShow -} \ No newline at end of file +} + +private val FEE_MAX_DIFF = BigDecimal("5") +private const val ZERO_DECIMALS = 0 \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt index 67fe86cf8e..cd1fe29939 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt @@ -15,6 +15,8 @@ internal sealed class FeeSelectorState { val customValues: ImmutableList = persistentListOf(), ) : FeeSelectorState() + data object Loading : FeeSelectorState() + data object Error : FeeSelectorState() } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt index ba181f423f..a74613acd3 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt @@ -45,8 +45,12 @@ internal class FeeStateFactory( val state = currentStateProvider() val feeState = state.feeState ?: return state return state.copy( - amountState = state.amountState?.copy(isFeeLoading = true), feeState = feeState.copy( + feeSelectorState = if (feeState.feeSelectorState is FeeSelectorState.Content) { + feeState.feeSelectorState + } else { + FeeSelectorState.Loading + }, notifications = persistentListOf(), isPrimaryButtonEnabled = false, ), @@ -73,7 +77,6 @@ internal class FeeStateFactory( val fee = feeConverter.convert(updatedFeeSelectorState) return state.copy( - amountState = state.amountState?.copy(isFeeLoading = false), feeState = feeState.copy( feeSelectorState = updatedFeeSelectorState, fee = fee, @@ -85,7 +88,6 @@ internal class FeeStateFactory( fun onFeeOnErrorState(): SendUiState { val state = currentStateProvider() return state.copy( - amountState = state.amountState?.copy(isFeeLoading = false), feeState = state.feeState?.copy( feeSelectorState = FeeSelectorState.Error, ), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt index d9b92b387d..f0cdd28425 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt @@ -14,7 +14,7 @@ internal class SendFeeStateConverter( override fun convert(value: Unit): SendStates.FeeState { return SendStates.FeeState( - feeSelectorState = FeeSelectorState.Error, + feeSelectorState = FeeSelectorState.Loading, fee = null, notifications = persistentListOf(), rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt index 8d7daa1665..a1fc8fbe25 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt @@ -38,7 +38,6 @@ internal class SendAmountFieldChangeConverter( return state.copy( amountState = amountState.copy( isPrimaryButtonEnabled = !isExceedBalance && !isZero, - subtractedFee = null, amountTextField = amountTextField.copy( value = cryptoValue, fiatValue = fiatValue, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt index 715d3ad4f8..b29459e757 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt @@ -34,7 +34,6 @@ internal class SendAmountFieldMaxAmountConverter( return state.copy( amountState = amountState.copy( isPrimaryButtonEnabled = true, - subtractedFee = null, amountTextField = amountTextField.copy( value = cryptoValue, fiatValue = fiatValue, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt index 91dbbde9bf..cbe162c0c5 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt @@ -22,10 +22,7 @@ internal object AmountStatePreviewData { walletBalance = stringReference("123.123"), tokenIconState = TokenIconState.Loading, segmentedButtonConfig = persistentListOf(), - notifications = persistentListOf(), - isFeeLoading = false, appCurrencyCode = "usd", - subtractedFee = null, amountTextField = SendTextField.AmountField( value = "123.123123123123123123", onValueChange = {}, @@ -49,6 +46,7 @@ internal object AmountStatePreviewData { isError = false, error = TextReference.EMPTY, ), + isSegmentedButtonsEnabled = true, ) val fiatAmountState = amountState.copy( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index 92be46efa2..10a3142e61 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -34,14 +34,12 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onRecipientMemoValueChange(value: String) {} - override fun feeReload(isToNextState: Boolean) {} + override fun feeReload() {} override fun onFeeSelectorClick(feeType: FeeType) {} override fun onCustomFeeValueChange(index: Int, value: String) {} - override fun onSubtractSelect() {} - override fun onReadMoreClick() {} override fun onSendClick() {} diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt index f6ccb6ba98..3b555a7e21 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt @@ -4,7 +4,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.utils.DateTimeFormatters -import com.tangem.core.ui.utils.toDateFormat +import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -81,7 +81,7 @@ internal class SendRecipientHistoryListConverter( } private fun TxHistoryItem.extractTimestamp(): TextReference { - val date = timestampInMillis.toDateFormat( + val date = timestampInMillis.toDateFormatWithTodayYesterday( formatter = DateTimeFormatters.dateDDMMYYYY, ) val time = timestampInMillis.toTimeFormat() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt index 855e5d3a3b..6b92680baf 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt @@ -18,22 +18,25 @@ internal class SendRecipientWalletListConverter : } } - private fun List.filterWallets() = this.filterNotNull() - .groupBy { item -> item.name } - .values.map { - it.mapIndexed { index, item -> - val name = if (it.size > 1) { - "${item.name} ${index.inc()}" - } else { - item.name + private fun List.filterWallets(): PersistentList { + var walletsCounter = 0 + return this.filterNotNull() + .groupBy { item -> item.name } + .values.map { + it.mapIndexed { index, item -> + val name = if (it.size > 1) { + "${item.name} ${index.inc()}" + } else { + item.name + } + SendRecipientListContent( + id = "${WALLET_KEY_TAG}${walletsCounter++}", + title = TextReference.Str(item.address), + subtitle = TextReference.Str(name), + ) } - SendRecipientListContent( - id = "${WALLET_KEY_TAG}$index", - title = TextReference.Str(item.address), - subtitle = TextReference.Str(name), - ) } - } - .flatten() - .toPersistentList() + .flatten() + .toPersistentList() + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index fda14b8a8a..604a9ba0a6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -78,7 +78,6 @@ private fun SendNavigationButton( val isFromConfirmation = currentState.isFromConfirmation val isCorrectScreen = currentState.type == SendUiStateType.Amount || currentState.type == SendUiStateType.Fee val isSendingState = currentState.type == SendUiStateType.Send && !isSuccess && !isSending - val showProgress = uiState.amountState?.isFeeLoading == true val (buttonTextId, buttonClick) = getButtonData( currentState = currentState, @@ -120,7 +119,7 @@ private fun SendNavigationButton( if (isSendingState) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) buttonClick() }, - showProgress = showProgress, + showProgress = false, modifier = Modifier.fillMaxWidth(), colors = TangemButtonsDefaults.primaryButtonColors, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt index 00af1e90e7..340f8e20ad 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt @@ -28,7 +28,7 @@ private const val AMOUNT_BUTTONS_KEY = "amountButtonsKey" internal fun LazyListScope.buttons( segmentedButtonConfig: PersistentList, clickIntents: SendClickIntents, - isMaxButtonEnabled: Boolean, + isSegmentedButtonsEnabled: Boolean, ) { item( key = AMOUNT_BUTTONS_KEY, @@ -48,15 +48,18 @@ internal fun LazyListScope.buttons( hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) clickIntents.onCurrencyChangeClick(it.isFiat) }, + isEnabled = isSegmentedButtonsEnabled, ) { - SendAmountCurrencyButton(it) + SendAmountCurrencyButton( + button = it, + isSegmentedButtonsEnabled = isSegmentedButtonsEnabled, + ) } } else { SpacerWMax() } SecondaryButton( text = stringResource(R.string.send_max_amount), - enabled = isMaxButtonEnabled, onClick = { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) clickIntents.onMaxValueClick() @@ -72,7 +75,7 @@ internal fun LazyListScope.buttons( } @Composable -private fun SendAmountCurrencyButton(button: SendAmountSegmentedButtonsConfig) { +private fun SendAmountCurrencyButton(button: SendAmountSegmentedButtonsConfig, isSegmentedButtonsEnabled: Boolean) { Row( modifier = Modifier .fillMaxSize() @@ -86,17 +89,16 @@ private fun SendAmountCurrencyButton(button: SendAmountSegmentedButtonsConfig) { FiatIcon( url = button.iconUrl, size = TangemTheme.dimens.size18, + isGrayscale = !isSegmentedButtonsEnabled, modifier = Modifier.size(TangemTheme.dimens.size18), ) - } else { - button.iconState?.let { - TokenIcon( - state = it, - shouldDisplayNetwork = false, - modifier = Modifier - .size(TangemTheme.dimens.size18), - ) - } + } else if (button.iconState != null) { + TokenIcon( + state = button.iconState, + shouldDisplayNetwork = false, + modifier = Modifier + .size(TangemTheme.dimens.size18), + ) } Text( text = button.title.resolveReference(), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt index bb5b97aa51..f92a93cdae 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt @@ -22,11 +22,10 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.rememberDecimalFormat import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import kotlinx.coroutines.delay import kotlinx.coroutines.job @Composable -internal fun AmountField(sendField: SendTextField.AmountField, isEnabled: Boolean, appCurrencyCode: String) { +internal fun AmountField(sendField: SendTextField.AmountField, appCurrencyCode: String) { val decimalFormat = rememberDecimalFormat() val isFiatValue = sendField.isFiatValue val currencyCode = if (isFiatValue) appCurrencyCode else null @@ -36,15 +35,6 @@ internal fun AmountField(sendField: SendTextField.AmountField, isEnabled: Boolea sendField.cryptoAmount to sendField.value } val requester = remember { FocusRequester() } - var isEnabledProxy by remember { mutableStateOf(isEnabled) } - - // Fix animation from amount screen to summary screen ([REDACTED_TASK_KEY]) - LaunchedEffect(key1 = isEnabled) { - if (isEnabled) { - delay(timeMillis = 700) - } - isEnabledProxy = isEnabled - } AmountTextField( value = primaryValue, @@ -62,7 +52,6 @@ internal fun AmountField(sendField: SendTextField.AmountField, isEnabled: Boolea color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, ), - isEnabled = isEnabledProxy, isAutoResize = true, modifier = Modifier .focusRequester(requester) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt index d9926e3f54..eb78c8d0a1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt @@ -62,7 +62,6 @@ internal fun LazyListScope.amountField( ) AmountField( sendField = amountState.amountTextField, - isEnabled = !amountState.isFeeLoading, appCurrencyCode = amountState.appCurrencyCode, ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt index 0f5b6493e8..6806468b2b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt @@ -12,7 +12,6 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.previewdata.AmountStatePreviewData import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub -import com.tangem.features.send.impl.presentation.ui.common.notifications import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents @Composable @@ -35,9 +34,8 @@ internal fun SendAmountContent( buttons( segmentedButtonConfig = amountState.segmentedButtonConfig, clickIntents = clickIntents, - isMaxButtonEnabled = !amountState.isFeeLoading, + isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled, ) - notifications(amountState.notifications) } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt index a3ee42ccbb..11423e7c73 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt @@ -1,21 +1,22 @@ package com.tangem.features.send.impl.presentation.ui.send +import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState @@ -26,20 +27,12 @@ import com.tangem.features.send.impl.presentation.utils.getFiatReference @Composable internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick: () -> Unit) { - val fee = feeState.fee ?: return - val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return - val (title, icon) = when (feeSelectorState.selectedFee) { - FeeType.Slow -> R.string.common_fee_selector_option_slow to R.drawable.ic_tortoise_24 - FeeType.Market -> R.string.common_fee_selector_option_market to R.drawable.ic_bird_24 - FeeType.Fast -> R.string.common_fee_selector_option_fast to R.drawable.ic_hare_24 - FeeType.Custom -> R.string.common_fee_selector_option_custom to R.drawable.ic_edit_24 - } Column( modifier = Modifier .fillMaxWidth() .clip(TangemTheme.shapes.roundedCornersXMedium) .background(TangemTheme.colors.background.action) - .clickable(enabled = !isSuccess) { onClick() } + .clickable(enabled = !isSuccess && feeState.fee != null) { onClick() } .padding(TangemTheme.dimens.spacing12), ) { Text( @@ -47,17 +40,71 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, ) - SelectorRowItem( - titleRes = title, - iconRes = icon, - preDot = getCryptoReference(fee.amount, feeState.isFeeApproximate), - postDot = getFiatReference(fee.amount, feeState.rate, feeState.appCurrency), - ellipsizeOffset = fee.amount.currencySymbol.length, - isSelected = true, - showDivider = false, - paddingValues = PaddingValues(), + + Box( modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), - ) + ) { + val feeSelectorState = feeState.feeSelectorState + val feeAmount = feeState.fee?.amount + val (title, icon) = if (feeSelectorState is FeeSelectorState.Content) { + when (feeSelectorState.selectedFee) { + FeeType.Slow -> R.string.common_fee_selector_option_slow to R.drawable.ic_tortoise_24 + FeeType.Market -> R.string.common_fee_selector_option_market to R.drawable.ic_bird_24 + FeeType.Fast -> R.string.common_fee_selector_option_fast to R.drawable.ic_hare_24 + FeeType.Custom -> R.string.common_fee_selector_option_custom to R.drawable.ic_edit_24 + } + } else { + R.string.common_fee_selector_option_market to R.drawable.ic_bird_24 + } + SelectorRowItem( + titleRes = title, + iconRes = icon, + preDot = getCryptoReference(feeAmount, feeState.isFeeApproximate), + postDot = feeAmount?.let { getFiatReference(it, feeState.rate, feeState.appCurrency) }, + ellipsizeOffset = feeAmount?.currencySymbol?.length, + isSelected = true, + showDivider = false, + paddingValues = PaddingValues(), + ) + FeeLoading(feeSelectorState) + FeeError(feeSelectorState) + } + } +} + +@Composable +private fun BoxScope.FeeLoading(feeSelectorState: FeeSelectorState) { + AnimatedContent( + targetState = feeSelectorState, + label = "Fee Loading State Change", + modifier = Modifier.align(Alignment.CenterEnd), + ) { + if (it == FeeSelectorState.Loading) { + RectangleShimmer( + radius = TangemTheme.dimens.radius3, + modifier = Modifier.size( + height = TangemTheme.dimens.size12, + width = TangemTheme.dimens.size90, + ), + ) + } + } +} + +@Composable +private fun BoxScope.FeeError(feeSelectorState: FeeSelectorState) { + AnimatedContent( + targetState = feeSelectorState, + label = "Fee Error State Change", + modifier = Modifier.align(Alignment.CenterEnd), + ) { + if (it == FeeSelectorState.Error) { + Text( + text = BigDecimalFormatter.EMPTY_BALANCE_SIGN, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.body2, + ) + } } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt index f276edf3c1..0801e63cd6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -40,6 +41,7 @@ internal fun RecipientBlock( .padding(TangemTheme.dimens.spacing12), ) { AddressBlock(recipientState.addressTextField) + MemoBlock(recipientState.memoTextField) } } @@ -70,6 +72,28 @@ private fun AddressBlock(address: SendTextField.RecipientAddress) { } } +@Composable +private fun MemoBlock(memo: SendTextField.RecipientMemo?) { + val showMemo = memo != null && memo.value.isNotBlank() + if (showMemo) { + HorizontalDivider( + color = TangemTheme.colors.icon.inactive, + modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12), + ) + Text( + text = memo?.label?.resolveReference().orEmpty(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.secondary, + ) + Text( + text = memo?.value.orEmpty(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), + ) + } +} + // region Preview @Preview @Composable diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index 9db5547b20..74e5d81128 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -39,14 +39,12 @@ internal interface SendClickIntents { // endregion // region Fee - fun feeReload(isToNextState: Boolean = false) + fun feeReload() fun onFeeSelectorClick(feeType: FeeType) fun onCustomFeeValueChange(index: Int, value: String) - fun onSubtractSelect() - fun onReadMoreClick() // endregion diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index ffad3f3633..caec97e044 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -24,6 +24,7 @@ import com.tangem.domain.tokens.* import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.transaction.error.GetFeeError @@ -156,10 +157,10 @@ internal class SendViewModel @Inject constructor( private val sendNotificationFactory = SendNotificationFactory( cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus }, - feePaidCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus }, currentStateProvider = Provider { uiState }, userWalletProvider = Provider { userWallet }, stateRouterProvider = Provider { stateRouter }, + isSubtractAvailableProvider = Provider { isAmountSubtractAvailable }, currencyChecksRepository = currencyChecksRepository, clickIntents = this, analyticsEventHandler = analyticsEventHandler, @@ -191,7 +192,6 @@ internal class SendViewModel @Inject constructor( private var qrScannerJobHolder = JobHolder() private var sendIdleTimer = 0L - private var feeIdleTimer = 0L init { subscribeOnCurrencyStatusUpdates() @@ -359,13 +359,14 @@ internal class SendViewModel @Inject constructor( stateRouter.showSend() } transactionId != null && amount != null && destinationAddress != null -> { + loadFee() uiState = stateFactory.getReadyState(amount, destinationAddress, memo) - stateRouter.showFee() + stateRouter.showSend() updateNotifications() } else -> { - getWalletsAndRecent() uiState = stateFactory.getReadyState() + getWalletsAndRecent() stateRouter.showRecipient() updateNotifications() } @@ -406,7 +407,8 @@ internal class SendViewModel @Inject constructor( return if (!isMultiCurrency) { val status = getCryptoCurrencyStatusSyncUseCase(walletId).getOrNull() val address = status?.value?.networkAddress.takeIf { - status?.currency?.network?.id == cryptoCurrency.network.id + status?.currency?.network?.id == cryptoCurrency.network.id && + status.currency.network.derivationPath !is Network.DerivationPath.Custom } address?.let { AvailableWallet( @@ -417,7 +419,8 @@ internal class SendViewModel @Inject constructor( } else { val statuses = getCryptoCurrencyStatusesSyncUseCase(walletId).getOrNull() val walletCurrency = statuses?.firstOrNull { - it.currency.network.id == cryptoCurrency.network.id + it.currency.network.id == cryptoCurrency.network.id && + it.currency.network.derivationPath !is Network.DerivationPath.Custom } val address = walletCurrency?.value?.networkAddress address?.let { @@ -484,12 +487,7 @@ internal class SendViewModel @Inject constructor( if (onFeeNext()) return } SendUiStateType.Amount -> { - if (uiState.feeState?.feeSelectorState is FeeSelectorState.Content) { - if (onFeeCoverageAlert()) return - } else { - loadFee(isToNextState = true) - return - } + loadFee() } else -> Unit } @@ -515,15 +513,15 @@ internal class SendViewModel @Inject constructor( innerRouter.openTokenDetails(userWalletId, currency) private fun onFeeNext(): Boolean { - if (onFeeCoverageAlert()) return true if (checkIfFeeTooLow(uiState)) { uiState = eventStateFactory.getFeeTooLowAlert( onConsume = { uiState = eventStateFactory.onConsumeEventState() }, ) return true } + val feeSelectorState = uiState.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false return checkIfFeeTooHigh( - state = uiState, + feeSelectorState = feeSelectorState, onShow = { diff -> uiState = eventStateFactory.getFeeTooHighAlert( diff = diff, @@ -533,19 +531,6 @@ internal class SendViewModel @Inject constructor( ) } - private fun onFeeCoverageAlert(): Boolean { - val isFeeCoverage = checkFeeCoverage(uiState, cryptoCurrencyStatus) - return if (isAmountSubtractAvailable && isFeeCoverage) { - uiState = eventStateFactory.getFeeCoverageAlert( - onConsume = { uiState = eventStateFactory.onConsumeEventState() }, - ) - true - } else { - analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount(false)) - false - } - } - private fun cancelFeeRequest() { viewModelScope.launch(dispatchers.main) { feeJobHolder.cancel() @@ -647,7 +632,7 @@ internal class SendViewModel @Inject constructor( // endregion // region fee - override fun feeReload(isToNextState: Boolean) = loadFee(isToNextState = isToNextState) + override fun feeReload() = loadFee() override fun onFeeSelectorClick(feeType: FeeType) { uiState = feeStateFactory.onFeeSelectedState(feeType) @@ -662,12 +647,6 @@ internal class SendViewModel @Inject constructor( updateFeeNotifications() } - override fun onSubtractSelect() { - uiState = stateFactory.onSubtractSelect(isAmountSubtractAvailable) - stateRouter.showSend() - analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount(true)) - } - override fun onReadMoreClick() { val locale = if (Locale.getDefault().language == RU_LOCALE) RU_LOCALE else EN_LOCALE val url = buildString { @@ -678,10 +657,7 @@ internal class SendViewModel @Inject constructor( innerRouter.openUrl(url) } - private fun loadFee(isToNextState: Boolean = false) { - // debouncing fee request - if (SystemClock.elapsedRealtime() - feeIdleTimer < FEE_UPDATE_DELAY) return - + private fun loadFee() { viewModelScope.launch(dispatchers.main) { val isShowStatus = uiState.feeState?.fee == null if (isShowStatus) { @@ -689,35 +665,25 @@ internal class SendViewModel @Inject constructor( } val result = callFeeUseCase()?.fold( ifRight = { - feeIdleTimer = SystemClock.elapsedRealtime() uiState = feeStateFactory.onFeeOnLoadedState(it) - if (isToNextState && !onFeeCoverageAlert()) { - stateRouter.showSend() - } }, ifLeft = { - onFeeLoadFailed(isShowStatus, isToNextState) + onFeeLoadFailed(isShowStatus) }, ) if (result == null) { - onFeeLoadFailed(isShowStatus, isToNextState) + onFeeLoadFailed(isShowStatus) } + updateNotifications() updateFeeNotifications() }.saveIn(feeJobHolder) .invokeOnCompletion { - uiState = amountStateFactory.getOnAmountFeeLoadingCancel() + // todo } } - private fun onFeeLoadFailed(isShowStatus: Boolean, isToNextState: Boolean) { - when { - isToNextState -> { - uiState = eventStateFactory.getFeeUnreachableErrorState { - uiState = eventStateFactory.onConsumeEventState() - } - } - isShowStatus -> uiState = feeStateFactory.onFeeOnErrorState() - } + private fun onFeeLoadFailed(isShowStatus: Boolean) { + if (isShowStatus) uiState = feeStateFactory.onFeeOnErrorState() } private suspend fun checkIfSubtractAvailable() { @@ -735,7 +701,7 @@ internal class SendViewModel @Inject constructor( return getFeeUseCase.invoke( amount = amount, destination = recipientState.addressTextField.value, - userWalletId = userWalletId, + userWallet = userWallet, cryptoCurrency = cryptoCurrency, ) } @@ -751,7 +717,6 @@ internal class SendViewModel @Inject constructor( verifyAndSendTransaction() } else { onCheckFeeUpdate() - feeIdleTimer = SystemClock.elapsedRealtime() sendIdleTimer = SystemClock.elapsedRealtime() } } @@ -792,10 +757,11 @@ internal class SendViewModel @Inject constructor( override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) { uiState = amountStateFactory.getOnAmountValueChange(reducedAmount.parseBigDecimal(cryptoCurrency.decimals)) uiState = sendNotificationFactory.dismissNotificationState(clazz) + feeReload() } override fun onNotificationCancel(clazz: Class) { - uiState = sendNotificationFactory.dismissNotificationState(clazz) + uiState = sendNotificationFactory.dismissNotificationState(clazz = clazz, isIgnored = true) } private fun verifyAndSendTransaction() { @@ -804,10 +770,18 @@ internal class SendViewModel @Inject constructor( val fee = feeState.fee ?: return val memo = uiState.recipientState?.memoTextField?.value val amountValue = uiState.amountState?.amountTextField?.cryptoAmount?.value ?: return + val feeValue = fee.amount.value ?: return + + val receivingAmount = checkAndCalculateSubtractedAmount( + isAmountSubtractAvailable = isAmountSubtractAvailable, + cryptoCurrencyStatus = cryptoCurrencyStatus, + amountValue = amountValue, + feeValue = feeValue, + ) viewModelScope.launch(dispatchers.main) { createTransactionUseCase( - amount = amountValue.convertToAmount(cryptoCurrency), + amount = receivingAmount.convertToAmount(cryptoCurrency), fee = fee, memo = memo, destination = recipient, @@ -920,7 +894,6 @@ internal class SendViewModel @Inject constructor( private companion object { const val CHECK_FEE_UPDATE_DELAY = 60_000L - const val FEE_UPDATE_DELAY = 10_000L const val BALANCE_UPDATE_DELAY = 10_000L const val RU_LOCALE = "ru" diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index a4b2c646ab..fc191d1d2d 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -124,14 +124,15 @@ class SwapDomainModule { @Provides @Singleton fun provideSendTransactionUseCase( - @SwapScope isDemoCardUseCase: IsDemoCardUseCase, cardSdkConfigRepository: CardSdkConfigRepository, transactionRepository: TransactionRepository, + walletManagersFacade: WalletManagersFacade, ): SendTransactionUseCase { return SendTransactionUseCase( - isDemoCardUseCase = isDemoCardUseCase, + demoConfig = DemoConfig(), cardSdkConfigRepository = cardSdkConfigRepository, transactionRepository = transactionRepository, + walletManagersFacade = walletManagersFacade, ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index a74ac1059c..c3fd4f4fd5 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -646,6 +646,7 @@ internal class StateBuilder( } private fun getWarningForError(dataError: DataError, fromToken: CryptoCurrency): SwapWarning { + val providerErrorMessage = getProviderErrorMessage(dataError) return when (dataError) { is DataError.ExchangeTooSmallAmountError -> SwapWarning.GeneralError( notificationConfig = NotificationConfig( @@ -674,10 +675,16 @@ internal class StateBuilder( } else { resourceReference(R.string.warning_express_refresh_required_title) }, - subtitle = if (dataError is DataError.UnknownError) { - resourceReference(R.string.common_unknown_error) - } else { - resourceReference(R.string.express_error_code, wrappedList(dataError.code.toString())) + subtitle = when { + dataError is DataError.UnknownError -> { + resourceReference(R.string.common_unknown_error) + } + providerErrorMessage != null -> { + providerErrorMessage + } + else -> { + resourceReference(R.string.express_error_code, wrappedList(dataError.code.toString())) + } }, iconResId = R.drawable.img_attention_20, buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( @@ -972,7 +979,7 @@ internal class StateBuilder( return uiState.copy( alert = SwapWarning.GenericWarning( message = if (swapTransactionState is SwapTransactionState.ExpressError) { - getAlertErrorMessage(swapTransactionState.dataError) + getProviderErrorMessage(swapTransactionState.dataError) } else { null }, @@ -987,7 +994,7 @@ internal class StateBuilder( ) } - private fun getAlertErrorMessage(dataError: DataError): TextReference? { + private fun getProviderErrorMessage(dataError: DataError): TextReference? { return when (dataError) { is DataError.SwapsAreUnavailableNowError -> resourceReference( id = R.string.express_error_swap_unavailable, @@ -998,6 +1005,7 @@ internal class StateBuilder( formatArgs = wrappedList(dataError.code), ) is DataError.ExchangeProviderNotActiveError, + is DataError.ExchangeProviderNotFoundError, is DataError.ExchangeProviderNotAvailableError, is DataError.ExchangeProviderProviderInternalError, -> resourceReference( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt index d5445e3d1a..55e6cba9b0 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt @@ -4,7 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.utils.BigDecimalFormatter -import com.tangem.core.ui.utils.toDateFormat +import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrency @@ -71,7 +71,7 @@ internal class TokenDetailsSwapTransactionsStateConverter( txUrl = transaction.status?.txExternalUrl, txExternalId = transaction.status?.txExternalId, timestamp = TextReference.Str( - "${timestamp.toDateFormat()}, ${timestamp.toTimeFormat()}", + "${timestamp.toDateFormatWithTodayYesterday()}, ${timestamp.toTimeFormat()}", ), fiatSymbol = appCurrency.symbol, statuses = getStatuses(transaction.status?.status), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt index 9fb6df70b1..6a8bc163c7 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt @@ -4,7 +4,7 @@ import androidx.paging.* import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState -import com.tangem.core.ui.utils.toDateFormat +import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents @@ -68,7 +68,7 @@ internal class TokenDetailsTxHistoryItemFlowConverter( // Use raw timestamp to get date // If [afterDate] is the first transaction in the flow, add the group title - val afterDate = after.getTimestamp()?.toDateFormat() ?: return@insertSeparators null + val afterDate = after.getTimestamp()?.toDateFormatWithTodayYesterday() ?: return@insertSeparators null if (before is TxHistoryItemState.Title) { return@insertSeparators TxHistoryItemState.GroupTitle( title = afterDate, @@ -80,7 +80,7 @@ internal class TokenDetailsTxHistoryItemFlowConverter( * If [beforeDate] is not equals to [afterDate], then [afterDate] is first transaction in * the new group */ - val beforeDate = before.getTimestamp()?.toDateFormat() ?: return@insertSeparators null + val beforeDate = before.getTimestamp()?.toDateFormatWithTodayYesterday() ?: return@insertSeparators null return@insertSeparators if (beforeDate != afterDate) { TxHistoryItemState.GroupTitle( title = afterDate, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 5722777eb6..26d69cf20d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -10,7 +10,6 @@ import com.tangem.blockchain.common.address.AddressType import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.deeplink.global.BuyCurrencyDeepLink -import com.tangem.core.deeplink.global.SellCurrencyDeepLink import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.extensions.resourceReference @@ -166,7 +165,6 @@ internal class TokenDetailsViewModel @Inject constructor( viewModel = this, deepLinks = listOf( BuyCurrencyDeepLink(::onBuyCurrencyDeepLink), - SellCurrencyDeepLink(::onSellCurrencyDeepLink), ), ) } @@ -176,20 +174,6 @@ internal class TokenDetailsViewModel @Inject constructor( analyticsEventsHandler.send(TokenScreenAnalyticsEvent.Bought(currency.symbol)) } - private fun onSellCurrencyDeepLink(data: SellCurrencyDeepLink.Data) { - sendCurrency( - status = cryptoCurrencyStatus ?: return, - transactionInfo = data.let { - TransactionInfo( - transactionId = it.transactionId, - destinationAddress = it.depositWalletAddress, - amount = it.baseCurrencyAmount, - tag = it.depositWalletAddressTag, - ) - }, - ) - } - override fun onCreate(owner: LifecycleOwner) { analyticsEventsHandler.send( event = TokenScreenAnalyticsEvent.DetailsScreenOpened(token = cryptoCurrency.symbol), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt index 59e2022b58..3c1e89fbeb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt @@ -8,16 +8,14 @@ import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.deeplink.global.BuyCurrencyDeepLink import com.tangem.core.deeplink.global.SellCurrencyDeepLink import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase -import com.tangem.domain.tokens.GetCryptoCurrencyUseCase -import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase -import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase +import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch @@ -29,23 +27,20 @@ internal class WalletDeepLinksHandler @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase, + private val getCryptoCurrencyStatusesSyncUseCase: GetCryptoCurrencyStatusesSyncUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, private val reduxStateHolder: ReduxStateHolder, ) { - private var deepLinks: List = emptyList() + private var deepLinksMap = mutableMapOf>() - fun registerForSingleCurrencyWallets(viewModel: ViewModel, userWallet: UserWallet) { - if (userWallet.isMultiCurrency) { - deepLinksRegistry.unregister(deepLinks) - } else { - if (deepLinks.isEmpty()) { - deepLinks = getDeepLinks(userWallet, viewModel.viewModelScope) - } - - deepLinksRegistry.register(deepLinks) + fun registerForWallet(viewModel: ViewModel, userWallet: UserWallet) { + val deepLinks = deepLinksMap.getOrPut(userWallet.walletId) { + getDeepLinks(userWallet, viewModel.viewModelScope) } + deepLinksRegistry.unregisterByIds(deepLinks.map { it.id }) + deepLinksRegistry.register(deepLinks) viewModel.addCloseable { deepLinksRegistry.unregister(deepLinks) @@ -60,20 +55,23 @@ internal class WalletDeepLinksHandler @Inject constructor( } }, ) - val buyCurrencyDeepLink = BuyCurrencyDeepLink( - onReceive = { - scope.launch { - onBuyCurrencyDeepLink(userWallet) - } - }, - ) - return listOf(sellCurrencyDeepLink, buyCurrencyDeepLink) + return buildList { + add(sellCurrencyDeepLink) + if (!userWallet.isMultiCurrency) { + add( + BuyCurrencyDeepLink( + onReceive = { + scope.launch { onBuyCurrencyDeepLink(userWallet) } + }, + ), + ) + } + } } private suspend fun onSellCurrencyDeepLink(userWallet: UserWallet, data: SellCurrencyDeepLink.Data) { - val cryptoCurrencyStatus = getCryptoCurrencyStatusSyncUseCase(userWallet.walletId) - .getOrNull() ?: return + val cryptoCurrencyStatus = findCryptoCurrencyStatus(userWallet, data.currencyId) ?: return val feeCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase( userWallet.walletId, cryptoCurrencyStatus, @@ -110,6 +108,19 @@ internal class WalletDeepLinksHandler @Inject constructor( analyticsEventHandler.send(TokenScreenAnalyticsEvent.Bought(cryptoCurrency.symbol)) } + private suspend fun findCryptoCurrencyStatus( + userWallet: UserWallet, + currencyIdValue: String, + ): CryptoCurrencyStatus? { + return if (userWallet.isMultiCurrency) { + getCryptoCurrencyStatusesSyncUseCase(userWallet.walletId).getOrNull()?.let { currencies -> + currencies.find { currencyIdValue == it.currency.id.value } + } + } else { + getCryptoCurrencyStatusSyncUseCase(userWallet.walletId).getOrNull() + } + } + private fun sendCoin( userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt index 62c49d5586..b24e902bfa 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt @@ -4,7 +4,7 @@ import androidx.paging.* import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState -import com.tangem.core.ui.utils.toDateFormat +import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.utils.converter.Converter import kotlinx.coroutines.CoroutineScope @@ -50,7 +50,7 @@ internal class TxHistoryItemFlowConverter( // Use raw timestamp to get date // If [afterDate] is the first transaction in the flow, add the group title - val afterDate = after.getTimestamp()?.toDateFormat() ?: return@insertSeparators null + val afterDate = after.getTimestamp()?.toDateFormatWithTodayYesterday() ?: return@insertSeparators null if (before is TxHistoryItemState.Title) { return@insertSeparators TxHistoryItemState.GroupTitle(afterDate, itemKey = UUID.randomUUID().toString()) } @@ -59,7 +59,7 @@ internal class TxHistoryItemFlowConverter( * If [beforeDate] is not equals to [afterDate], then [afterDate] is first transaction in * the new group */ - val beforeDate = before.getTimestamp()?.toDateFormat() ?: return@insertSeparators null + val beforeDate = before.getTimestamp()?.toDateFormatWithTodayYesterday() ?: return@insertSeparators null return@insertSeparators if (beforeDate != afterDate) { TxHistoryItemState.GroupTitle(afterDate, itemKey = UUID.randomUUID().toString()) } else { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index eb6f9dc8b2..e74ecbdb0b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -140,7 +140,7 @@ internal class WalletViewModel @Inject constructor( selectedWalletAnalyticsSender.send(selectedWallet) } - walletDeepLinksHandler.registerForSingleCurrencyWallets( + walletDeepLinksHandler.registerForWallet( viewModel = this, userWallet = selectedWallet, ) diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index cf10f1ce54..a4492852b0 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -33,8 +33,15 @@ object BlockchainUtils { return blockchain == Blockchain.Bitcoin || blockchain == Blockchain.BitcoinTestnet } + /** If current [networkId] is Dogecoin */ fun isDogecoin(networkId: String): Boolean { val blockchain = Blockchain.fromId(networkId) return blockchain == Blockchain.Dogecoin } + + /** If current [networkId] is Tezos */ + fun isTezos(networkId: String): Boolean { + val blockchain = Blockchain.fromId(networkId) + return blockchain == Blockchain.Tezos + } } \ No newline at end of file From d174ada664b548727b8e38a5f96879ab47284a94 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Apr 2024 15:18:48 +0800 Subject: [PATCH 35/98] Updated on 2026-08-14 --- .../com/tangem/tap/LockUserWalletsTimer.kt | 4 +- .../main/java/com/tangem/tap/MainActivity.kt | 21 ++- .../common/redux/global/GlobalMiddleware.kt | 2 +- .../walletconnect/WalletConnectSdkHelper.kt | 5 +- .../details/redux/DetailsMiddleware.kt | 3 + .../walletconnect/WalletConnectMiddleware.kt | 2 +- .../details/ui/details/DetailsFragment.kt | 5 + .../details/ui/details/DetailsViewModel.kt | 3 +- .../tap/features/home/redux/HomeMiddleware.kt | 4 +- .../features/onboarding/OnboardingHelper.kt | 6 +- .../twins/redux/TwinCardsMiddleware.kt | 4 +- .../redux/OnboardingWalletMiddleware.kt | 1 + .../saveWallet/redux/SaveWalletMiddleware.kt | 7 +- .../welcome/redux/WelcomeMiddleware.kt | 12 +- .../CryptoCurrencyConverter.kt | 10 +- .../tap/proxy/TransactionManagerImpl.kt | 3 +- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 122 +----------------- .../com/tangem/tap/proxy/di/ProxyModule.kt | 11 +- .../api/tangemTech/TangemTechServiceApi.kt | 2 +- .../tangem/lib/crypto/UserWalletManager.kt | 33 ----- 20 files changed, 71 insertions(+), 189 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index f668224cad..bb6e616898 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.lifecycleScope import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchWithMain @@ -17,6 +18,7 @@ internal class LockUserWalletsTimer( owner: LifecycleOwner, private val settingsRepository: SettingsRepository, private val duration: Duration = with(Duration) { 10.minutes }, + private val userWalletsListManager: UserWalletsListManager, ) : LifecycleOwner by owner, DefaultLifecycleObserver { @@ -108,7 +110,7 @@ internal class LockUserWalletsTimer( delay(duration) if (isActive) { - val userWalletsListManager = userWalletsListManagerSafe?.asLockable() ?: return@launch + val userWalletsListManager = userWalletsListManager.asLockable() ?: return@launch if (userWalletsListManager.hasUserWallets) { val currentTime = System.currentTimeMillis() diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 2109f0bb6b..94171ca9e9 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -37,9 +37,9 @@ import com.tangem.data.card.sdk.CardSdkLifecycleObserver import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase -import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable import com.tangem.feature.qrscanning.QrScanningRouter @@ -93,14 +93,6 @@ private val mainCoroutineContext: CoroutineContext get() = Job() + Dispatchers.Main + FeatureCoroutineExceptionHandler.create("mainScope") val mainScope = CoroutineScope(mainCoroutineContext) -// TODO: will be remove in this task [REDACTED_JIRA] -@Deprecated(message = "Provide UserWalletsListManager using DI") -val userWalletsListManagerSafe: UserWalletsListManager? get() = store.state.globalState.userWalletsListManager - -// TODO: will be remove in this task [REDACTED_JIRA] -@Deprecated(message = "Provide UserWalletsListManager using DI") -val userWalletsListManager: UserWalletsListManager get() = userWalletsListManagerSafe!! - @Suppress("LargeClass") @AndroidEntryPoint class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder { @@ -157,6 +149,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var analyticsEventsHandler: AnalyticsEventHandler + @Inject + lateinit var userWalletsListManager: UserWalletsListManager + internal val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow @@ -221,7 +216,11 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac tangemSdkManager = injectedTangemSdkManager appStateHolder.tangemSdkManager = tangemSdkManager backupService = BackupService.init(cardSdkConfigRepository.sdk, this) - lockUserWalletsTimer = LockUserWalletsTimer(owner = this, settingsRepository = settingsRepository) + lockUserWalletsTimer = LockUserWalletsTimer( + owner = this, + settingsRepository = settingsRepository, + userWalletsListManager = userWalletsListManager, + ) initIntentHandlers() @@ -307,7 +306,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } private fun initIntentHandlers() { - val hasSavedWalletsProvider = { store.state.globalState.userWalletsListManager?.hasUserWallets == true } + val hasSavedWalletsProvider = { userWalletsListManager.hasUserWallets } intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope)) intentProcessor.addHandler(WalletConnectLinkIntentHandler()) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index 921898e383..8b6a2476ca 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -26,7 +26,6 @@ import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store -import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -107,6 +106,7 @@ private fun handleAction(action: Action, appState: () -> AppState?) { scope.launch { val scanResponseProvider: () -> ScanResponse? = { + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) userWalletsListManager.selectedUserWalletSync?.scanResponse } val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index 16bc4b280d..cd502dc24e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -36,7 +36,6 @@ import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionReque import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager import timber.log.Timber import java.math.BigDecimal import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam @@ -44,6 +43,10 @@ import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam @Suppress("LargeClass") class WalletConnectSdkHelper { + private val userWalletsListManager by lazy { + store.inject(DaggerGraphState::generalUserWalletsListManager) + } + @Suppress("MagicNumber") suspend fun prepareTransactionData(data: EthTransactionData): WcTransactionData? { val transaction = data.transaction diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 39dc386ee6..edef362685 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -79,6 +79,8 @@ class DetailsMiddleware { class EraseWalletMiddleware { @Suppress("CyclomaticComplexMethod") fun handle(action: DetailsAction.ResetToFactory) { + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) + when (action) { is DetailsAction.ResetToFactory.Start -> { val card = store.state.detailsState.cardSettingsState?.card ?: return @@ -523,6 +525,7 @@ class DetailsMiddleware { val userWallet = UserWalletBuilder(scanResponse).build() ?: return CompletionResult.Failure(TangemSdkError.WalletIsNotCreated()) + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) return userWalletsListManager.save(userWallet) .doOnSuccess { store.onUserWalletSelected(userWallet) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index f29861dbbc..b480ea809c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -20,7 +20,6 @@ import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store -import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware @@ -174,6 +173,7 @@ class WalletConnectMiddleware { private suspend fun getWalletManagers(): List { val walletManagerFacade = store.inject(DaggerGraphState::walletManagersFacade) + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) val userWallet = userWalletsListManager.selectedUserWalletSync ?: return emptyList() return walletManagerFacade.getStoredWalletManagers(userWallet.walletId) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt index d050b873e5..56880350cc 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt @@ -10,6 +10,7 @@ import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.feedback.GetSupportFeedbackEmailUseCase +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.features.details.redux.DetailsState @@ -36,6 +37,9 @@ internal class DetailsFragment : ComposeFragment(), StoreSubscriber = mutableStateOf(updateState(store.state.detailsState)) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 2048292209..862e4ccb8a 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -22,7 +22,6 @@ import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store -import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action @@ -109,6 +108,7 @@ private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch { return@launch } + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) userWalletsListManager.save(userWallet) .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") @@ -128,6 +128,8 @@ private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) { ) if (currency != null) { + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) + Analytics.send( event = Basic.SignedIn( currency = currency, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 33784bac12..0c6c5601a2 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -14,12 +14,15 @@ import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.domain.userwallets.UserWalletIdBuilder -import com.tangem.tap.* import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.extensions.* import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.saveWallet.redux.SaveWalletAction +import com.tangem.tap.mainScope import com.tangem.tap.proxy.redux.DaggerGraphState +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch import timber.log.Timber @@ -148,6 +151,7 @@ object OnboardingHelper { return } + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) userWalletsListManager.save(userWallet, canOverride = true) .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index dcb56e9eb5..a1c84cb01a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -32,7 +32,6 @@ import com.tangem.tap.mainScope import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store -import com.tangem.tap.userWalletsListManager import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -61,6 +60,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { val globalState = store.state.globalState val onboardingManager = globalState.onboardingState.onboardingManager val twinCardsState = store.state.twinCardsState + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) fun getScanResponse(): ScanResponse { return when (twinCardsState.mode) { @@ -366,6 +366,8 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } private fun getPopBackScreen(): AppScreen { + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) + return if (userWalletsListManager.hasUserWallets) { val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync } .fold(onSuccess = { true }, onFailure = { false }) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index f4248022b7..3a6b93da1a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -556,6 +556,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) return@launch } + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) userWalletsListManager.update( userWalletId = userWallet.walletId, update = { wallet -> diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 563c72cd24..bdfffe8f9f 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -8,7 +8,6 @@ import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.domain.wallets.models.UserWallet -import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Onboarding @@ -17,7 +16,11 @@ import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState +import com.tangem.tap.mainScope import com.tangem.tap.proxy.redux.DaggerGraphState +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn import kotlinx.coroutines.launch @@ -70,6 +73,7 @@ internal class SaveWalletMiddleware { return@launch } + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) userWalletsListManager.save(userWallet, canOverride = true) .flatMap { saveAccessCodeIfNeeded(accessCode = backupInfo.accessCode, cardsInWallet = userWallet.cardsInWallet) @@ -104,6 +108,7 @@ internal class SaveWalletMiddleware { * because it will be automatically saved on UserWalletsListManager switch */ + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { val error = IllegalStateException("No selected user wallet") Timber.e(error, "Unable to save user wallet") diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index b59a973b64..4956441790 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -16,13 +16,16 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType import com.tangem.domain.wallets.legacy.unlockIfLockable -import com.tangem.tap.* +import com.tangem.tap.backupService import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler import com.tangem.tap.proxy.redux.DaggerGraphState +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.rekotlin.Middleware @@ -83,6 +86,7 @@ internal class WelcomeMiddleware { """.trimIndent(), ) + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) userWalletsListManager.unlockIfLockable(type = UnlockType.ANY) .doOnFailure { error -> Timber.e(error, "Unable to unlock user wallets with biometrics") @@ -115,6 +119,7 @@ internal class WelcomeMiddleware { scanCardInternal { scanResponse -> val userWallet = UserWalletBuilder(scanResponse).build() ?: return@scanCardInternal + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) userWalletsListManager.save(userWallet, canOverride = true) .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") @@ -140,12 +145,14 @@ internal class WelcomeMiddleware { ) Analytics.addContext(scanResponse) if (currency != null) { + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) + Analytics.send( event = Basic.SignedIn( currency = currency, batch = scanResponse.card.batchId, signInType = signInType, - walletsCount = store.state.globalState.userWalletsListManager?.walletsCount.toString(), + walletsCount = userWalletsListManager.walletsCount.toString(), hasBackup = scanResponse.card.backupStatus?.isActive, ), ) @@ -153,6 +160,7 @@ internal class WelcomeMiddleware { } private suspend fun disableUserWalletsSaving() { + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) userWalletsListManager.clear() .flatMap { tangemSdkManager.clearSavedUserCodes() } .doOnFailure { e -> diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CryptoCurrencyConverter.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CryptoCurrencyConverter.kt index 7ed890dda8..041b9561ee 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CryptoCurrencyConverter.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CryptoCurrencyConverter.kt @@ -5,7 +5,9 @@ import com.tangem.blockchain.common.Token import com.tangem.data.tokens.utils.CryptoCurrencyFactory import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.tap.common.extensions.inject import com.tangem.tap.domain.model.Currency +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.utils.converter.TwoWayConverter @@ -20,9 +22,7 @@ internal class CryptoCurrencyConverter : TwoWayConverter { - // FIXME: Find user wallet by ID - val userWallet = requireNotNull(userWalletsStore.selectedUserWalletOrNull) { - "No user wallet selected" - } - return currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWallet.walletId) - .filter { - val checkCustom = if (isExcludeCustom) { - !it.isCustom - } else { - true - } - val blockchain = Blockchain.fromId(it.network.id.value) - - blockchain.toNetworkId() == networkId && - checkCustom && - it.network.derivationPath.value == derivationPath - } - .map { - val blockchain = Blockchain.fromId(it.network.id.value) - - if (it is CryptoCurrency.Token) { - NonNativeToken( - id = it.id.rawCurrencyId ?: "", - name = it.name, - symbol = it.symbol, - networkId = blockchain.toNetworkId(), - contractAddress = it.contractAddress, - decimalCount = it.decimals, - ) - } else { - NativeToken( - id = it.id.rawCurrencyId ?: "", - name = it.name, - symbol = it.symbol, - networkId = blockchain.toNetworkId(), - ) - } - } - } - - override fun getNativeTokenForNetwork(networkId: String): Currency { - val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } - return NativeToken( - id = blockchain.toCoinId(), - name = blockchain.fullName, - symbol = blockchain.currency, - networkId = networkId, - ) - } - override fun getWalletId(): String { val selectedUserWallet = requireNotNull( userWalletsListManager.selectedUserWalletSync, @@ -88,18 +23,6 @@ class UserWalletManagerImpl( return selectedUserWallet.walletId.stringValue } - override suspend fun isTokenAdded(currency: Currency, derivationPath: String?): Boolean { - val blockchain = requireNotNull(Blockchain.fromNetworkId(currency.networkId)) { "blockchain not found" } - return try { - val walletManager = getActualWalletManager(blockchain, derivationPath) - walletManager.cardTokens.any { - it.id == currency.id - } - } catch (e: IllegalArgumentException) { - false - } - } - override suspend fun hideAllTokens() { // FIXME: Used only in Tester Actions Timber.w("Not implemented") @@ -119,37 +42,6 @@ class UserWalletManagerImpl( ?.hash } - override suspend fun getCurrentWalletTokensBalance( - networkId: String, - extraTokens: List, - derivationPath: String?, - ): Map { - val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } - val walletManager = getActualWalletManager(blockchain, derivationPath) - - // workaround for get balance for tokens that doesn't exist in wallet - val extraTokensToLoadBalance = extraTokens - .filterIsInstance() - .map { - it.toSdkToken() - } - .filter { - !walletManager.cardTokens.contains(it) - } - walletManager.addTokens(extraTokensToLoadBalance) - walletManager.update() - val balances = walletManager.wallet.amounts.map { entry -> - val amount = entry.value - amount.currencySymbol to ProxyAmount( - amount.currencySymbol, - amount.value ?: BigDecimal.ZERO, - amount.decimals, - ) - }.toMap() - extraTokensToLoadBalance.forEach { walletManager.removeToken(it) } - return balances - } - override suspend fun getNativeTokenBalance(networkId: String, derivationPath: String?): ProxyAmount? { val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } val walletManager = getActualWalletManager(blockchain, derivationPath) @@ -179,14 +71,4 @@ class UserWalletManagerImpl( "No wallet manager found" } } -} - -private fun NonNativeToken.toSdkToken(): Token { - return Token( - id = this.id, - name = this.name, - symbol = this.symbol, - contractAddress = this.contractAddress, - decimals = this.decimalCount, - ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt index d3f44858b1..37fc1060b4 100644 --- a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt +++ b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt @@ -1,9 +1,8 @@ package com.tangem.tap.proxy.di -import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager import com.tangem.tap.proxy.* @@ -28,13 +27,11 @@ internal object ProxyModule { @Singleton fun provideUserWalletManager( walletManagersFacade: WalletManagersFacade, - currenciesRepository: CurrenciesRepository, - userWalletsStore: UserWalletsStore, + userWalletsListManager: UserWalletsListManager, ): UserWalletManager { return UserWalletManagerImpl( walletManagersFacade = walletManagersFacade, - currenciesRepository = currenciesRepository, - userWalletsStore = userWalletsStore, + userWalletsListManager = userWalletsListManager, ) } @@ -44,11 +41,13 @@ internal object ProxyModule { appStateHolder: AppStateHolder, cardSdkConfigRepository: CardSdkConfigRepository, walletManagersFacade: WalletManagersFacade, + userWalletsListManager: UserWalletsListManager, ): TransactionManager { return TransactionManagerImpl( appStateHolder = appStateHolder, cardSdkConfigRepository = cardSdkConfigRepository, walletManagersFacade = walletManagersFacade, + userWalletsListManager = userWalletsListManager, ) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechServiceApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechServiceApi.kt index 107db49e49..f5a24561f8 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechServiceApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechServiceApi.kt @@ -11,7 +11,7 @@ import retrofit2.http.Header */ interface TangemTechServiceApi { - @GET("v1/networks") + @GET("networks/providers") suspend fun getBlockchainProviders( @Header("card_public_key") cardPublicKey: String, @Header("card_id") cardId: String, diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt index c2e37da872..9bdde82e08 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt @@ -1,6 +1,5 @@ package com.tangem.lib.crypto -import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.ProxyAmount /** @@ -8,28 +7,11 @@ import com.tangem.lib.crypto.models.ProxyAmount */ interface UserWalletManager { - /** - * Returns all user tokens (merged from local and backend) - */ - @Throws(IllegalStateException::class) - suspend fun getUserTokens(networkId: String, derivationPath: String?, isExcludeCustom: Boolean): List - - @Throws(IllegalStateException::class) - fun getNativeTokenForNetwork(networkId: String): Currency - /** * Returns user walletId or empty string */ fun getWalletId(): String - /** - * Checks that token added to user wallet - * - * @param currency to receive referral payments - */ - @Throws(IllegalStateException::class) - suspend fun isTokenAdded(currency: Currency, derivationPath: String?): Boolean - suspend fun hideAllTokens() /** @@ -41,21 +23,6 @@ interface UserWalletManager { @Throws(IllegalStateException::class) suspend fun getWalletAddress(networkId: String, derivationPath: String?): String - /** - * Return balances from wallet found by networkId - * - * @param networkId - * @param extraTokens tokens you want to check balance that not exists in wallet - * @param derivationPath if null uses default - * @return map of - */ - @Throws(IllegalStateException::class) - suspend fun getCurrentWalletTokensBalance( - networkId: String, - extraTokens: List, - derivationPath: String?, - ): Map - @Throws(IllegalStateException::class) suspend fun getNativeTokenBalance(networkId: String, derivationPath: String?): ProxyAmount? From 6c4b0a31ec8d7ec167fad738ad565f756af48c6c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 10:28:08 +0200 Subject: [PATCH 36/98] Updated on 2026-08-14 --- .../kotlin/com/tangem/common/BaseTestCase.kt | 1 - .../kotlin/com/tangem/tests/ScanErrorTest.kt | 48 +++++++++++ .../tangem/tap/domain/sdk/mocks/MockConfig.kt | 8 -- .../sdk/mocks/{Mocks.kt => MockContent.kt} | 6 +- .../tap/domain/sdk/mocks/MockProvider.kt | 82 ++++++++++++------- .../Wallet2MockContent.kt} | 6 +- .../WalletMockContent.kt} | 6 +- 7 files changed, 111 insertions(+), 46 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/tangem/tests/ScanErrorTest.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockConfig.kt rename app/src/main/java/com/tangem/tap/domain/sdk/mocks/{Mocks.kt => MockContent.kt} (89%) rename app/src/main/java/com/tangem/tap/domain/sdk/mocks/{wallet2/Wallet2Mocks.kt => content/Wallet2MockContent.kt} (91%) rename app/src/main/java/com/tangem/tap/domain/sdk/mocks/{wallet/WalletMocks.kt => content/WalletMockContent.kt} (99%) diff --git a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt index 7f38b804c2..4b3278bc9f 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt @@ -3,7 +3,6 @@ package com.tangem.common import android.Manifest import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.espresso.intent.Intents -import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.rule.GrantPermissionRule import com.kaspersky.components.composesupport.config.withComposeSupport diff --git a/app/src/androidTest/kotlin/com/tangem/tests/ScanErrorTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/ScanErrorTest.kt new file mode 100644 index 0000000000..a1d67d8c1c --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/ScanErrorTest.kt @@ -0,0 +1,48 @@ +package com.tangem.tests + +import com.tangem.common.BaseTestCase +import com.tangem.screens.DisclaimerScreen +import com.tangem.screens.StoriesScreen +import com.tangem.screens.WalletScreen +import com.tangem.tap.domain.sdk.mocks.MockProvider +import dagger.hilt.android.testing.HiltAndroidTest +import io.github.kakaocup.compose.node.element.ComposeScreen +import org.junit.Test + +@HiltAndroidTest +class ScanErrorTest : BaseTestCase() { + + @Test + fun goToMain() = + setupHooks().run { + ComposeScreen.onComposeScreen(composeTestRule) { + step("Click on \"Scan\" button emulating scan error") { + MockProvider.setEmulateError() + scanButton { + assertIsDisplayed() + performClick() + } + } + step("Click on \"Scan\" button again without emulating error") { + MockProvider.resetEmulateError() + scanButton { + assertIsDisplayed() + performClick() + } + } + } + DisclaimerScreen { + step("Click on \"Accept\" button") { + acceptButton { + isVisible() + click() + } + } + } + ComposeScreen.onComposeScreen(composeTestRule) { + step("Make sure wallet screen is visible") { + assertIsDisplayed() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockConfig.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockConfig.kt deleted file mode 100644 index 4ae2e1429d..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockConfig.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.tap.domain.sdk.mocks - -import arrow.core.Either -import com.tangem.domain.models.scan.ProductType - -data class MockConfig( - val content: Either, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/Mocks.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockContent.kt similarity index 89% rename from app/src/main/java/com/tangem/tap/domain/sdk/mocks/Mocks.kt rename to app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockContent.kt index 5aaa566f29..bb8ca7deab 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/Mocks.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockContent.kt @@ -8,7 +8,7 @@ import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse -interface Mocks { +interface MockContent { val successResponse: SuccessResponse @@ -22,11 +22,15 @@ interface Mocks { val createProductWalletTaskResponse: CreateProductWalletTaskResponse + // Wallet2-specific val importWalletResponse: CreateProductWalletTaskResponse + // Twin-specific val finalizeTwinResponse: ScanResponse + // Twin-specific val createFirstTwinResponse: CreateWalletResponse + // Twin-specific val createSecondTwinResponse: CreateWalletResponse } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt index b4658912c7..9468912e1c 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt @@ -1,59 +1,81 @@ package com.tangem.tap.domain.sdk.mocks import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemError +import com.tangem.common.core.TangemSdkError import com.tangem.domain.models.scan.ProductType -import com.tangem.tap.domain.sdk.mocks.wallet.WalletMocks -import com.tangem.tap.domain.sdk.mocks.wallet2.Wallet2Mocks +import com.tangem.tap.domain.sdk.mocks.content.WalletMockContent +import com.tangem.tap.domain.sdk.mocks.content.Wallet2MockContent import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse object MockProvider { - private var mocks: Mocks = getMocks(ProductType.Wallet) + private var content: MockContent = getMockContent(ProductType.Wallet) - fun setMockConfig(mockConfig: MockConfig) { - mockConfig.content.fold( - ifLeft = { - mocks = getMocks(it) - }, - ifRight = { - mocks = it - }, - ) + private var emulateError: Boolean = false + + private var emulatedError: TangemError = TangemSdkError.TagLost() + + fun setEmulateError(error: TangemError? = null) { + emulateError = true + error?.let { + emulatedError = it + } } - fun getSuccessResponse() = CompletionResult.Success(mocks.successResponse) + fun resetEmulateError() { + emulateError = false + } - fun getScanResponse() = CompletionResult.Success(mocks.scanResponse) + fun setMocks(productType: ProductType) { + content = getMockContent(productType) + } - fun getDerivationTaskResponse() = CompletionResult.Success(mocks.derivationTaskResponse) + fun setMocks(mockContent: MockContent) { + content = mockContent + } - fun getCardDto() = CompletionResult.Success(mocks.cardDto) + fun getSuccessResponse() = CompletionResult.Success(content.successResponse).orFailure() - fun getExtendedPublicKey() = CompletionResult.Success(mocks.extendedPublicKey) + fun getScanResponse() = CompletionResult.Success(content.scanResponse).orFailure() + + fun getDerivationTaskResponse() = CompletionResult.Success(content.derivationTaskResponse).orFailure() + + fun getCardDto() = CompletionResult.Success(content.cardDto).orFailure() + + fun getExtendedPublicKey() = CompletionResult.Success(content.extendedPublicKey).orFailure() fun getCreateProductWalletResponse(): CompletionResult { - return CompletionResult.Success(mocks.createProductWalletTaskResponse) + return CompletionResult.Success(content.createProductWalletTaskResponse).orFailure() } fun getImportWalletResponse(): CompletionResult { - return CompletionResult.Success(mocks.importWalletResponse) - } - - private fun getMocks(productType: ProductType): Mocks { - return when (productType) { - ProductType.Wallet -> WalletMocks - ProductType.Wallet2 -> Wallet2Mocks - else -> TODO() - } + return CompletionResult.Success(content.importWalletResponse).orFailure() } // region Twin-specific - fun finalizeTwin() = CompletionResult.Success(mocks.finalizeTwinResponse) + fun finalizeTwin() = CompletionResult.Success(content.finalizeTwinResponse).orFailure() - fun createFirstTwinWallet() = CompletionResult.Success(mocks.createFirstTwinResponse) + fun createFirstTwinWallet() = CompletionResult.Success(content.createFirstTwinResponse).orFailure() - fun createSecondTwinWallet() = CompletionResult.Success(mocks.createSecondTwinResponse) + fun createSecondTwinWallet() = CompletionResult.Success(content.createSecondTwinResponse).orFailure() // endregion + + private fun getMockContent(productType: ProductType): MockContent { + return when (productType) { + ProductType.Wallet -> WalletMockContent + ProductType.Wallet2 -> Wallet2MockContent + else -> TODO() + } + } + + private fun CompletionResult.Success.orFailure(): CompletionResult { + return if (emulateError) { + CompletionResult.Failure(emulatedError) + } else { + this + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet2/Wallet2Mocks.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2MockContent.kt similarity index 91% rename from app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet2/Wallet2Mocks.kt rename to app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2MockContent.kt index fca86fd05e..2b9a29b938 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet2/Wallet2Mocks.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2MockContent.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.domain.sdk.mocks.wallet2 +package com.tangem.tap.domain.sdk.mocks.content import com.tangem.common.SuccessResponse import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey @@ -6,10 +6,10 @@ import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.wallet.CreateWalletResponse -import com.tangem.tap.domain.sdk.mocks.Mocks +import com.tangem.tap.domain.sdk.mocks.MockContent import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse -object Wallet2Mocks : Mocks { +object Wallet2MockContent : MockContent { override val scanResponse: ScanResponse get() = TODO("Not yet implemented") diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet/WalletMocks.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt similarity index 99% rename from app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet/WalletMocks.kt rename to app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt index 3beb9d2dae..8e059f5450 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/wallet/WalletMocks.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.domain.sdk.mocks.wallet +package com.tangem.tap.domain.sdk.mocks.content import com.tangem.common.SuccessResponse import com.tangem.common.card.* @@ -13,11 +13,11 @@ import com.tangem.operations.backup.PrimaryCard import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.operations.wallet.CreateWalletResponse -import com.tangem.tap.domain.sdk.mocks.Mocks +import com.tangem.tap.domain.sdk.mocks.MockContent import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import java.util.Date -object WalletMocks : Mocks { +object WalletMockContent : MockContent { private val primaryCard = PrimaryCard( cardId = "AC05000000086747", From 06941914cd1780201163fa04965baadecc3b635b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Apr 2024 20:38:16 +0800 Subject: [PATCH 37/98] Updated on 2026-08-14 --- .../java/com/tangem/tap/TangemApplication.kt | 2 - .../tap/common/redux/global/GlobalAction.kt | 2 - .../common/redux/global/GlobalMiddleware.kt | 34 +------------- .../tap/common/redux/global/GlobalReducer.kt | 7 --- .../tap/common/redux/global/GlobalState.kt | 2 - .../tangem/tap/features/main/MainViewModel.kt | 46 +++++++++++++++++-- 6 files changed, 43 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index c6f72f4025..5a22b825cb 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -195,8 +195,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { // [REDACTED_JIRA] runBlocking { featureTogglesManager.init() - - store.dispatch(GlobalAction.UpdateUserWalletsListManager(generalUserWalletsListManager)) } val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index a9c23d448f..4fb2375cf9 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -9,7 +9,6 @@ import com.tangem.datasource.config.models.ChatConfig import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.tap.common.feedback.FeedbackData import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.redux.DebugErrorAction @@ -101,6 +100,5 @@ sealed class GlobalAction : Action { data class Success(val countryCode: String) : GlobalAction() } - data class UpdateUserWalletsListManager(val manager: UserWalletsListManager) : GlobalAction() data class ChangeAppThemeMode(val appThemeMode: AppThemeMode) : GlobalAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index 8b6a2476ca..b50d350a28 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -3,9 +3,7 @@ package com.tangem.tap.common.redux.global import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard -import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.StateDialog import com.tangem.datasource.config.models.Config import com.tangem.domain.appcurrency.model.AppCurrency @@ -15,7 +13,6 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.send.redux.SendAction -import com.tangem.tap.mainScope import com.tangem.tap.network.exchangeServices.BuyExchangeService import com.tangem.tap.network.exchangeServices.CardExchangeRules import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager @@ -26,12 +23,10 @@ import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware -import timber.log.Timber import java.util.Locale object GlobalMiddleware { @@ -148,33 +143,6 @@ private fun handleAction(action: Action, appState: () -> AppState?) { } } } - is GlobalAction.UpdateUserWalletsListManager -> { - val walletManagersFacade = store.inject(DaggerGraphState::walletManagersFacade) - - /* - * If implementation of the UserWalletsListManager is changed, - * then all observers of selectedUserWallet become irrelevant. - */ - action.manager.selectedUserWallet - .distinctUntilChanged() - .onEach { userWallet -> - Analytics.setContext(userWallet.scanResponse) - Analytics.send(Basic.WalletOpened()) - - store.state.globalState.feedbackManager?.infoHolder?.let { infoHolder -> - infoHolder.setCardInfo(userWallet.scanResponse) - - walletManagersFacade - .getAll(userWallet.walletId) - .distinctUntilChanged() - .onEach(infoHolder::setWalletsInfo) - .catch { Timber.e(it) } - .launchIn(mainScope) - } - } - .flowOn(Dispatchers.IO) - .launchIn(scope) - } } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 9cfcf4182c..d85d35f457 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -3,12 +3,10 @@ package com.tangem.tap.common.redux.global import com.tangem.domain.redux.domainStore import com.tangem.domain.redux.global.DomainGlobalAction import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.proxy.AppStateHolder -import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.utils.extensions.replaceBy import org.rekotlin.Action @@ -94,11 +92,6 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde userCountryCode = action.countryCode, ) } - is GlobalAction.UpdateUserWalletsListManager -> { - val generalUserWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) - - globalState.copy(userWalletsListManager = generalUserWalletsListManager) - } is GlobalAction.ChangeAppThemeMode -> globalState.copy( appThemeMode = action.appThemeMode, ) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index 20712881c8..7a75d16d87 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -5,7 +5,6 @@ import com.tangem.datasource.config.ConfigManager import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.domain.TapWalletManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager @@ -27,7 +26,6 @@ data class GlobalState( val dialog: StateDialog? = null, val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(), val userCountryCode: String? = null, - val userWalletsListManager: UserWalletsListManager? = null, val appThemeMode: AppThemeMode = AppThemeMode.DEFAULT, ) : StateType diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index ae12f26669..6c4c24f485 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -3,6 +3,8 @@ package com.tangem.tap.features.main import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.blockchainsdk.BlockchainSDKFactory +import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.core.navigation.ReduxNavController @@ -13,11 +15,16 @@ import com.tangem.domain.balancehiding.ListenToFlipsUseCase import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.tap.common.extensions.setContext import com.tangem.tap.features.main.model.MainScreenState +import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") @@ -30,6 +37,8 @@ internal class MainViewModel @Inject constructor( private val deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase, private val incrementAppLaunchCounterUseCase: IncrementAppLaunchCounterUseCase, private val blockchainSDKFactory: BlockchainSDKFactory, + private val userWalletsListManager: UserWalletsListManager, + private val walletManagersFacade: WalletManagersFacade, private val dispatchers: CoroutineDispatcherProvider, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel(), MainIntents { @@ -47,10 +56,7 @@ internal class MainViewModel @Inject constructor( private set init { - viewModelScope.launch(dispatchers.main) { - blockchainSDKFactory.init() - isSplashScreenShown = false - } + loadApplicationResources() viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() } @@ -64,6 +70,38 @@ internal class MainViewModel @Inject constructor( } } + /** Loading the resources needed to run the application */ + private fun loadApplicationResources() { + viewModelScope.launch(dispatchers.main) { + blockchainSDKFactory.init() + prepareSelectedWalletFeedback() + + isSplashScreenShown = false + } + } + + private fun prepareSelectedWalletFeedback() { + userWalletsListManager.selectedUserWallet + .distinctUntilChanged() + .onEach { userWallet -> + Analytics.setContext(userWallet.scanResponse) + Analytics.send(Basic.WalletOpened()) + + store.state.globalState.feedbackManager?.infoHolder?.let { infoHolder -> + infoHolder.setCardInfo(userWallet.scanResponse) + + walletManagersFacade + .getAll(userWallet.walletId) + .distinctUntilChanged() + .onEach(infoHolder::setWalletsInfo) + .catch { Timber.e(it) } + .launchIn(viewModelScope) + } + } + .flowOn(dispatchers.io) + .launchIn(viewModelScope) + } + private fun updateAppCurrencies() { viewModelScope.launch(dispatchers.main) { fetchAppCurrenciesUseCase.invoke() From 396bc35770820c9c15dd8dcd24ecc06901ac4efd Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 11:35:55 +0200 Subject: [PATCH 38/98] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 3 - core/res/src/main/res/values-ru/strings.xml | 26 +-- core/res/src/main/res/values/strings.xml | 26 +-- .../domain/exchange/RampStateManager.kt | 2 + .../tokens/GetCryptoCurrencyActionsUseCase.kt | 173 ++++++------------ .../model/ScenarioUnavailabilityReason.kt | 21 ++- .../state/factory/TokenDetailsStateFactory.kt | 47 +++-- .../WalletCurrencyActionsClickIntents.kt | 46 +++-- 8 files changed, 126 insertions(+), 218 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index be433d70bb..6821d4a992 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -6,7 +6,6 @@ import com.tangem.domain.tokens.* import com.tangem.domain.tokens.repository.* import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.feature.swap.domain.api.SwapRepository -import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -190,7 +189,6 @@ internal object TokensDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, - sendFeatureToggles: SendFeatureToggles, dispatchers: CoroutineDispatcherProvider, ): GetCryptoCurrencyActionsUseCase { return GetCryptoCurrencyActionsUseCase( @@ -199,7 +197,6 @@ internal object TokensDomainModule { currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, - sendFeatureToggles = sendFeatureToggles, dispatchers = dispatchers, ) } diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index da22a118ee..adb56faef9 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -4,16 +4,12 @@ Валюты Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств. Обратиться в поддержку - Попробовать снова Эта функция недоступна в демонстрационном режиме Причина: %s Не могу отправить транзакцию Выбранный кошелёк не поддерживает сеть %1$s Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. - Спасибо за ваш отзыв. Мы ответим в кратчайшие сроки. - Ваши предложения отправлены - Пожалуйста, попробуйте приложить карту в точности, как показано на анимации, или запросите поддержку. У вас возникли трудности со сканированием карты? Эта карта не предназначена для работы с этим приложением Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. @@ -28,8 +24,6 @@ Тёмная Светлая Как в системе - При выборе настройки как в системе приложение будет использовать тему в соответствии с настройками вашего устройства - Системная Тема Настройки приложения Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" @@ -81,7 +75,6 @@ Создать Удалить Отключено - Отключить Готово Включить Включено @@ -114,7 +107,6 @@ Отклонить Перезагрузить Переименовать - Повторить Сохранить изменения Искать Поиск токенов @@ -138,7 +130,6 @@ Я понял Произошла ошибка. Пожалуйста, попробуйте снова. Недоступно - Предупреждение Да Адрес контракта скопирован! Доступные сети @@ -185,7 +176,6 @@ Скрывать балансы жестом переворота Эмитент Подписано - Если вы забудете код, то потеряете доступ к своим средствам. Восстановление кода невозможно. Подробности Проверьте подключение с интернетом или переключитесь на другую сеть Условия использования @@ -551,12 +541,12 @@ Балансы показаны Отменить Выбранная операция в данный момент недоступна. Попробуйте позже. - В данный момент покупка монеты %s недоступна. Но мы работаем над её добавлением. - У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. - Выбранная операция в данный момент недоступна. Попробуйте позже. - Обмен %s не доступен. Но мы работаем над его добавлением. - В данный момент продажа монеты %s недоступна. Но мы работаем над её добавлением. - Выберите адрес + В данный момент покупка монеты %s недоступна. Следите за нашими обновлениями. + У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. + У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. + В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. + Продажа средств станет доступной после завершения транзакции %s + В данный момент продажа %s недоступна. Следите за нашими обновлениями. Сгенерировать XPUB Скрыть Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. @@ -667,9 +657,10 @@ Возможно, данная карта - образец или подделка Ошибка проверки подлинности Ассоциировать - Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%.4f %s + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%1$s %2$s Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять Ассоциируете свой токен + Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. Малое количество подписей Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. @@ -689,6 +680,7 @@ Карта уже подписывала транзакции Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше Нравится Tangem? + Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его Необходима плата за аренду сети %1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети. Недостаточно %1$s для оплаты комиссии сети diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index efe98087bb..f40c8e0daa 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -4,16 +4,12 @@ Manage tokens Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. Request support - Try again This feature is disabled in Demo mode Reason: %s Can\'t send a transaction The selected does not support the %1$s network To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. Tokens in %1$s network are not supported by this card due to firmware limitation. - Thank you for your feedback. We will respond as soon as possible - Your suggestions were sent - Please try to tap the card exactly as shown in the animation or request support. Are you having difficulty scanning your card? This card is not designed to work with this app Default Fee @@ -29,8 +25,6 @@ Dark Light System default - If system is selected, the app will auto-adjust based on your device\'s system settings - System Theme App Settings To hide or show your balances, simply flip your device screen down, or switch it off in Settings @@ -80,7 +74,6 @@ Create Delete Disabled - Disconnect Done Enable Enabled @@ -113,7 +106,6 @@ Reject Reload Rename - Retry Save changes Search Search tokens @@ -137,7 +129,6 @@ I understand There was an error. Please try again. Unreachable - Warning Yes Contract address copied! Available networks @@ -184,7 +175,6 @@ Flip-to-Hide Balances Issuer Signed - If you forget the code you will lose access to your funds. Code recovery is not possible. Details Check your internet connection or switch to a different network Terms of Service @@ -552,12 +542,12 @@ Balances shown Undo This operation is currently unavailable. Please try again later. - The purchase of the %s is currently unavailable. But we are working on adding it. - You do not have funds to send. Top up your account to be able to send funds from it. - This operation is currently unavailable. Please try again later. - %s swap is not available. But we are working on adding it. - Sell of the %s coin is currently unavailable. But we are working on adding it. - Choose address + Buying %s is not available at the moment. Please check our updates. + You do not have funds to sell. Top up your account to be able to sell funds from it. + You do not have funds to send. Top up your account to be able to send funds from it. + Swapping %s is not available at the moment. Please check our updates. + Selling funds will be available once the %s transaction is complete + Selling %s is not available at the moment. Please check our updates. Generate XPUB Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. @@ -668,9 +658,10 @@ This card might be a production sample or counterfeit Authenticity check failed Associate - This token must be associated with your Hedera account before you can receive it. Association fee ~%.4f %s + This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s This token must be associated with your Hedera account before you can receive it Associate your token + Hot enough %s. Top up your Hedera account to associate this token Only %s signatures are left on this card. You must withdraw all of your funds. Low signature count Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. @@ -688,6 +679,7 @@ Card has already signed transactions Your review keeps us motivated to make Tangem Wallet even better Enjoying Tangem? + You must associate your token before receiving tokens Network rent fee required %1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. Insufficient %1$s to cover network fee diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt index 29712ab27c..ac9b4fa892 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt @@ -6,6 +6,8 @@ import com.tangem.domain.tokens.model.CryptoCurrency * Manager that holds info about available actions as Sell and Buy */ interface RampStateManager { + fun availableForBuy(cryptoCurrency: CryptoCurrency): Boolean + fun availableForSell(cryptoCurrency: CryptoCurrency): Boolean } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index abd618d4b1..74e89db52c 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -9,13 +9,10 @@ import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.isNullOrZero import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* -import java.math.BigDecimal /** * Use case to determine which TokenActions are available for a [CryptoCurrency] @@ -29,7 +26,6 @@ class GetCryptoCurrencyActionsUseCase( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, - private val sendFeatureToggles: SendFeatureToggles, private val dispatchers: CoroutineDispatcherProvider, ) { @@ -82,8 +78,9 @@ class GetCryptoCurrencyActionsUseCase( /** * Creates list of action for expected order - * Actions priority: [Buy Send Receive Sell Swap] + * Actions priority: [Receive Send Swap Buy Sell] */ + @Suppress("CyclomaticComplexMethod", "LongMethod") private suspend fun createListOfActions( userWallet: UserWallet, coinStatus: CryptoCurrencyStatus?, @@ -112,7 +109,6 @@ class GetCryptoCurrencyActionsUseCase( // send val sendUnavailabilityReason = getSendUnavailabilityReason( - userWalletId = userWallet.walletId, cryptoCurrencyStatus = cryptoCurrencyStatus, coinStatus = coinStatus, ) @@ -124,7 +120,10 @@ class GetCryptoCurrencyActionsUseCase( // swap if (userWallet.isMultiCurrency) { - if (marketCryptoCurrencyRepository.isExchangeable(userWallet.walletId, cryptoCurrency)) { + if ( + marketCryptoCurrencyRepository.isExchangeable(userWallet.walletId, cryptoCurrency) && + cryptoCurrencyStatus.value !is CryptoCurrencyStatus.NoQuote + ) { activeList.add(TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.None)) } else { disabledList.add( @@ -145,14 +144,42 @@ class GetCryptoCurrencyActionsUseCase( } // sell - if (rampManager.availableForSell(cryptoCurrency)) { - activeList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.None)) - } else { - disabledList.add( - TokenActionsState.ActionState.Sell( - unavailabilityReason = ScenarioUnavailabilityReason.SellUnavailable(cryptoCurrency.name), - ), - ) + val sellSupportedByService = rampManager.availableForSell(cryptoCurrency) + val sendAvailable = sendUnavailabilityReason is ScenarioUnavailabilityReason.None + + when { + sellSupportedByService && sendAvailable -> { + activeList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.None)) + } + sellSupportedByService && !sendAvailable -> { + (sendUnavailabilityReason as? ScenarioUnavailabilityReason.EmptyBalance)?.let { + disabledList.add( + TokenActionsState.ActionState.Sell( + unavailabilityReason = it.copy( + withdrawalScenario = ScenarioUnavailabilityReason.WithdrawalScenario.SELL, + ), + ), + ) + } + (sendUnavailabilityReason as? ScenarioUnavailabilityReason.PendingTransaction)?.let { + disabledList.add( + TokenActionsState.ActionState.Sell( + unavailabilityReason = it.copy( + withdrawalScenario = ScenarioUnavailabilityReason.WithdrawalScenario.SELL, + ), + ), + ) + } + } + else -> { + disabledList.add( + TokenActionsState.ActionState.Sell( + unavailabilityReason = ScenarioUnavailabilityReason.NotSupportedBySellService( + cryptoCurrency.name, + ), + ), + ) + } } // hide @@ -180,9 +207,9 @@ class GetCryptoCurrencyActionsUseCase( ), ) } - actionsList.add(TokenActionsState.ActionState.Send(ScenarioUnavailabilityReason.NoQuotes)) - actionsList.add(TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.NoQuotes)) - actionsList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.NoQuotes)) + actionsList.add(TokenActionsState.ActionState.Send(ScenarioUnavailabilityReason.Unreachable)) + actionsList.add(TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.Unreachable)) + actionsList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.Unreachable)) if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { actionsList.add(TokenActionsState.ActionState.Receive(ScenarioUnavailabilityReason.None)) @@ -191,34 +218,22 @@ class GetCryptoCurrencyActionsUseCase( return actionsList } - private suspend fun getSendUnavailabilityReason( - userWalletId: UserWalletId, + private fun getSendUnavailabilityReason( cryptoCurrencyStatus: CryptoCurrencyStatus, coinStatus: CryptoCurrencyStatus?, ): ScenarioUnavailabilityReason { - val insufficientFundsForFee = insufficientFundsForFee( - userWalletId = userWalletId, - tokenStatus = cryptoCurrencyStatus, - coinStatus = coinStatus, - ) - return when { cryptoCurrencyStatus.value.amount.isNullOrZero() -> { - ScenarioUnavailabilityReason.EmptyBalance - } - insufficientFundsForFee != null -> { - ScenarioUnavailabilityReason.InsufficientFundsForFee( - currencyName = insufficientFundsForFee.currencyName, - networkName = insufficientFundsForFee.networkName, - feeCurrencyName = insufficientFundsForFee.feeCurrencyName, - feeCurrencySymbol = insufficientFundsForFee.feeCurrencySymbol, - ) + ScenarioUnavailabilityReason.EmptyBalance(ScenarioUnavailabilityReason.WithdrawalScenario.SEND) } currenciesRepository.hasPendingTransactions( cryptoCurrencyStatus = cryptoCurrencyStatus, coinStatus = coinStatus, ) -> { - ScenarioUnavailabilityReason.PendingTransaction(coinStatus?.currency?.symbol.orEmpty()) + ScenarioUnavailabilityReason.PendingTransaction( + withdrawalScenario = ScenarioUnavailabilityReason.WithdrawalScenario.SEND, + cryptoCurrencySymbol = coinStatus?.currency?.symbol.orEmpty(), + ) } else -> { ScenarioUnavailabilityReason.None @@ -226,93 +241,7 @@ class GetCryptoCurrencyActionsUseCase( } } - private suspend fun insufficientFundsForFee( - userWalletId: UserWalletId, - tokenStatus: CryptoCurrencyStatus, - coinStatus: CryptoCurrencyStatus?, - ): FeeInfo? { - val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, tokenStatus.currency) - coinStatus ?: return null - return when { - sendFeatureToggles.isRedesignedSendEnabled && !tokenStatus.value.amount.isZero() -> { - null - } - feePaidCurrency is FeePaidCurrency.Coin && - !tokenStatus.value.amount.isZero() && - coinStatus.value.amount.isZero() -> { - FeeInfo( - currencyName = tokenStatus.currency.name, - networkName = coinStatus.currency.network.name, - feeCurrencyName = coinStatus.currency.name, - feeCurrencySymbol = coinStatus.currency.symbol, - ) - } - feePaidCurrency is FeePaidCurrency.SameCurrency && !tokenStatus.value.amount.isZero() -> { - FeeInfo( - currencyName = tokenStatus.currency.name, - networkName = coinStatus.currency.network.name, - feeCurrencyName = coinStatus.currency.name, - feeCurrencySymbol = coinStatus.currency.symbol, - ) - } - feePaidCurrency is FeePaidCurrency.Token -> { - val feePaidTokenBalance = feePaidCurrency.balance - val amount = tokenStatus.value.amount ?: return null - if (!amount.isZero() && feePaidTokenBalance.isZero()) { - constructTokenBalanceNotEnoughWarning( - userWalletId = userWalletId, - tokenStatus = tokenStatus, - feePaidToken = feePaidCurrency, - ) - } else { - null - } - } - else -> null - } - } - - private suspend fun constructTokenBalanceNotEnoughWarning( - userWalletId: UserWalletId, - tokenStatus: CryptoCurrencyStatus, - feePaidToken: FeePaidCurrency.Token, - ): FeeInfo { - val token = currenciesRepository - .getMultiCurrencyWalletCurrenciesSync(userWalletId) - .find { - it is CryptoCurrency.Token && - it.contractAddress.equals(feePaidToken.contractAddress, ignoreCase = true) && - it.network.derivationPath == tokenStatus.currency.network.derivationPath - } - return if (token != null) { - FeeInfo( - currencyName = tokenStatus.currency.name, - networkName = token.network.name, - feeCurrencyName = feePaidToken.name, - feeCurrencySymbol = feePaidToken.symbol, - ) - } else { - FeeInfo( - currencyName = tokenStatus.currency.name, - networkName = tokenStatus.currency.network.name, - feeCurrencyName = feePaidToken.name, - feeCurrencySymbol = feePaidToken.symbol, - ) - } - } - private fun isAddressAvailable(networkAddress: NetworkAddress?): Boolean { return networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty() } - - private fun BigDecimal?.isZero(): Boolean { - return this?.signum() == 0 - } - - data class FeeInfo( - val currencyName: String, - val networkName: String, - val feeCurrencyName: String, - val feeCurrencySymbol: String, - ) } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt index a3b36c4216..27315e15a7 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt @@ -3,15 +3,12 @@ package com.tangem.domain.tokens.model sealed class ScenarioUnavailabilityReason { data object None : ScenarioUnavailabilityReason() - // send-specific - data class PendingTransaction(val cryptoCurrencySymbol: String) : ScenarioUnavailabilityReason() - data object EmptyBalance : ScenarioUnavailabilityReason() - data class InsufficientFundsForFee( - val currencyName: String, - val networkName: String, - val feeCurrencyName: String, - val feeCurrencySymbol: String, + // send&sell-specific + data class PendingTransaction( + val withdrawalScenario: WithdrawalScenario, + val cryptoCurrencySymbol: String, ) : ScenarioUnavailabilityReason() + data class EmptyBalance(val withdrawalScenario: WithdrawalScenario) : ScenarioUnavailabilityReason() // buy-specific data class BuyUnavailable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() @@ -20,7 +17,11 @@ sealed class ScenarioUnavailabilityReason { data class NotExchangeable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() // sell-specific - data class SellUnavailable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() + data class NotSupportedBySellService(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() - data object NoQuotes : ScenarioUnavailabilityReason() + data object Unreachable : ScenarioUnavailabilityReason() + + enum class WithdrawalScenario { + SELL, SEND + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index cd21841371..0109f66fa5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -334,29 +334,27 @@ internal class TokenDetailsStateFactory( private fun getUnavailabilityReasonText(unavailabilityReason: ScenarioUnavailabilityReason): TextReference { return when (unavailabilityReason) { - // send is ScenarioUnavailabilityReason.PendingTransaction -> { - resourceReference( - id = R.string.warning_send_blocked_pending_transactions_message, - formatArgs = wrappedList(unavailabilityReason.cryptoCurrencySymbol), - ) + when (unavailabilityReason.withdrawalScenario) { + ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference( + id = R.string.warning_send_blocked_pending_transactions_message, + formatArgs = wrappedList(unavailabilityReason.cryptoCurrencySymbol), + ) + ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference( + id = R.string.token_button_unavailability_reason_pending_transaction_sell, + formatArgs = wrappedList(unavailabilityReason.cryptoCurrencySymbol), + ) + } } - ScenarioUnavailabilityReason.EmptyBalance -> { - resourceReference( - id = R.string.token_button_unavailability_reason_empty_balance, - ) - } - is ScenarioUnavailabilityReason.InsufficientFundsForFee -> { - resourceReference( - id = R.string.warning_send_blocked_funds_for_fee_message, - formatArgs = wrappedList( - unavailabilityReason.currencyName, - unavailabilityReason.networkName, - unavailabilityReason.currencyName, - unavailabilityReason.feeCurrencyName, - unavailabilityReason.feeCurrencySymbol, - ), - ) + is ScenarioUnavailabilityReason.EmptyBalance -> { + when (unavailabilityReason.withdrawalScenario) { + ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference( + id = R.string.token_button_unavailability_reason_empty_balance_send, + ) + ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference( + id = R.string.token_button_unavailability_reason_empty_balance_sell, + ) + } } is ScenarioUnavailabilityReason.BuyUnavailable -> { resourceReference( @@ -370,18 +368,17 @@ internal class TokenDetailsStateFactory( formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName), ) } - is ScenarioUnavailabilityReason.SellUnavailable -> { + is ScenarioUnavailabilityReason.NotSupportedBySellService -> { resourceReference( id = R.string.token_button_unavailability_reason_sell_unavailable, formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName), ) } - ScenarioUnavailabilityReason.NoQuotes -> { + ScenarioUnavailabilityReason.Unreachable -> { resourceReference( - id = R.string.token_button_unavailability_reason_no_quotes, + id = R.string.token_button_unavailability_generic_description, ) } - ScenarioUnavailabilityReason.None -> { throw IllegalArgumentException("The unavailability reason must be other than None") } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 16fa32b0e7..3294411975 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -446,29 +446,27 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( private fun getUnavailabilityReasonText(unavailabilityReason: ScenarioUnavailabilityReason): TextReference { return when (unavailabilityReason) { - // send is ScenarioUnavailabilityReason.PendingTransaction -> { - resourceReference( - id = R.string.warning_send_blocked_pending_transactions_message, - formatArgs = wrappedList(unavailabilityReason.cryptoCurrencySymbol), - ) + when (unavailabilityReason.withdrawalScenario) { + ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference( + id = R.string.warning_send_blocked_pending_transactions_message, + formatArgs = wrappedList(unavailabilityReason.cryptoCurrencySymbol), + ) + ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference( + id = R.string.token_button_unavailability_reason_pending_transaction_sell, + formatArgs = wrappedList(unavailabilityReason.cryptoCurrencySymbol), + ) + } } - ScenarioUnavailabilityReason.EmptyBalance -> { - resourceReference( - id = R.string.token_button_unavailability_reason_empty_balance, - ) - } - is ScenarioUnavailabilityReason.InsufficientFundsForFee -> { - resourceReference( - id = R.string.warning_send_blocked_funds_for_fee_message, - formatArgs = wrappedList( - unavailabilityReason.currencyName, - unavailabilityReason.networkName, - unavailabilityReason.currencyName, - unavailabilityReason.feeCurrencyName, - unavailabilityReason.feeCurrencySymbol, - ), - ) + is ScenarioUnavailabilityReason.EmptyBalance -> { + when (unavailabilityReason.withdrawalScenario) { + ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference( + id = R.string.token_button_unavailability_reason_empty_balance_send, + ) + ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference( + id = R.string.token_button_unavailability_reason_empty_balance_sell, + ) + } } is ScenarioUnavailabilityReason.BuyUnavailable -> { resourceReference( @@ -482,15 +480,15 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName), ) } - is ScenarioUnavailabilityReason.SellUnavailable -> { + is ScenarioUnavailabilityReason.NotSupportedBySellService -> { resourceReference( id = R.string.token_button_unavailability_reason_sell_unavailable, formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName), ) } - ScenarioUnavailabilityReason.NoQuotes -> { + ScenarioUnavailabilityReason.Unreachable -> { resourceReference( - id = R.string.token_button_unavailability_reason_no_quotes, + id = R.string.token_button_unavailability_generic_description, ) } ScenarioUnavailabilityReason.None -> { From 78a69e656bab19c584b3407a6fbab8b0f05174bd Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 11:48:03 +0200 Subject: [PATCH 39/98] Updated on 2026-08-14 --- .../feature/swap/models/SwapStateHolder.kt | 1 - .../tangem/feature/swap/models/UiActions.kt | 1 - .../tangem/feature/swap/ui/StateBuilder.kt | 54 +++++++------------ .../feature/swap/ui/SwapScreenContent.kt | 1 - .../feature/swap/viewmodels/SwapViewModel.kt | 6 --- 5 files changed, 19 insertions(+), 44 deletions(-) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index e5db542bba..74d4f48e01 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -20,7 +20,6 @@ data class SwapStateHolder( val alert: SwapWarning.GenericWarning? = null, val changeCardsButtonState: ChangeCardsButtonState = ChangeCardsButtonState.ENABLED, val providerState: ProviderState, - val reduceAmountIgnore: Boolean, // ignore warning about reducing XTZ amount by 0.01 val fee: FeeItemState = FeeItemState.Empty, val permissionState: SwapPermissionState = SwapPermissionState.Empty, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt index bd84a05cac..2a873d453c 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt @@ -15,7 +15,6 @@ data class UiActions( val onBackClicked: () -> Unit, val onMaxAmountSelected: () -> Unit, val onReduceAmount: (SwapAmount) -> Unit, - val onReduceAmountIgnoreClick: () -> Unit, val openPermissionBottomSheet: () -> Unit, val onChangeApproveType: (ApproveType) -> Unit, // region new actions diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index c3fd4f4fd5..ec0eeb5e66 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -88,7 +88,6 @@ internal class StateBuilder( onShowPermissionBottomSheet = actions.openPermissionBottomSheet, providerState = ProviderState.Empty(), shouldShowMaxAmount = false, - reduceAmountIgnore = false, priceImpact = PriceImpact.Empty(), ) } @@ -217,7 +216,6 @@ internal class StateBuilder( val warnings = getWarningsForSuccessState( quoteModel = quoteModel, fromToken = fromToken, - ignoreAmountReduce = uiStateHolder.reduceAmountIgnore, ) val feeState = createFeeState(quoteModel.txFee, selectedFeeType) val fromCurrencyStatus = quoteModel.fromTokenInfo.cryptoCurrencyStatus @@ -316,10 +314,9 @@ internal class StateBuilder( private fun getWarningsForSuccessState( quoteModel: SwapState.QuotesLoadedState, fromToken: CryptoCurrency, - ignoreAmountReduce: Boolean, ): List { val warnings = mutableListOf() - maybeAddDomainWarnings(quoteModel, warnings, ignoreAmountReduce) + maybeAddDomainWarnings(quoteModel, warnings) maybeAddNeedReserveToCreateAccountWarning(quoteModel, warnings) maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken) maybeAddNetworkFeeCoverageWarning(quoteModel, warnings) @@ -355,11 +352,7 @@ internal class StateBuilder( } } - private fun maybeAddDomainWarnings( - quoteModel: SwapState.QuotesLoadedState, - warnings: MutableList, - ignoreAmountReduce: Boolean, - ) { + private fun maybeAddDomainWarnings(quoteModel: SwapState.QuotesLoadedState, warnings: MutableList) { quoteModel.warnings.forEach { when (it) { is Warning.ExistentialDepositWarning -> { @@ -397,23 +390,20 @@ internal class StateBuilder( ) } is Warning.ReduceAmountWarning -> { - if (!ignoreAmountReduce) { - warnings.add( - SwapWarning.ReduceAmount( - notificationConfig = createReduceAmountNotificationConfig( - amount = it.tezosFeeThreshold.toPlainString(), - onConfirmClick = { - val fromAmount = quoteModel.fromTokenInfo.tokenAmount - val patchedAmount = fromAmount.copy( - value = fromAmount.value - it.tezosFeeThreshold, - ) - actions.onReduceAmount(patchedAmount) - }, - onDismissClick = actions.onReduceAmountIgnoreClick, - ), + warnings.add( + SwapWarning.ReduceAmount( + notificationConfig = createReduceAmountNotificationConfig( + amount = it.tezosFeeThreshold.toPlainString(), + onConfirmClick = { + val fromAmount = quoteModel.fromTokenInfo.tokenAmount + val patchedAmount = fromAmount.copy( + value = fromAmount.value - it.tezosFeeThreshold, + ) + actions.onReduceAmount(patchedAmount) + }, ), - ) - } + ), + ) } } } @@ -1353,20 +1343,14 @@ internal class StateBuilder( ) } - private fun createReduceAmountNotificationConfig( - amount: String, - onConfirmClick: () -> Unit, - onDismissClick: () -> Unit, - ): NotificationConfig { + private fun createReduceAmountNotificationConfig(amount: String, onConfirmClick: () -> Unit): NotificationConfig { return NotificationConfig( title = resourceReference(R.string.send_notification_high_fee_title), subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(amount)), iconResId = R.drawable.img_attention_20, - buttonsState = NotificationConfig.ButtonsState.PairButtonsConfig( - primaryText = resourceReference(R.string.xtz_withdrawal_message_reduce, wrappedList(amount)), - onPrimaryClick = onConfirmClick, - secondaryText = resourceReference(R.string.xtz_withdrawal_message_ignore), - onSecondaryClick = onDismissClick, + buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig( + text = resourceReference(R.string.xtz_withdrawal_message_reduce, wrappedList(amount)), + onClick = onConfirmClick, ), ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index e68875f3ee..f0441ea811 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -497,7 +497,6 @@ private val state = SwapStateHolder( providerState = ProviderState.Loading(), priceImpact = PriceImpact.Empty(), shouldShowMaxAmount = true, - reduceAmountIgnore = false, tosState = TosState( tosLink = LegalState( title = stringReference("Terms of Use"), diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index d9598de96f..e78235321b 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -881,12 +881,6 @@ internal class SwapViewModel @Inject constructor( }, onMaxAmountSelected = ::onMaxAmountClicked, onReduceAmount = ::onReduceAmountClicked, - onReduceAmountIgnoreClick = { - uiState = uiState.copy( - reduceAmountIgnore = true, - warnings = uiState.warnings.filter { it !is SwapWarning.ReduceAmount }, - ) - }, openPermissionBottomSheet = { singleTaskScheduler.cancelTask() analyticsEventHandler.send(SwapEvents.ButtonGivePermissionClicked) From 2596a73c7bf48053aa37fa578eeac53c97790c7d Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 13:04:58 +0300 Subject: [PATCH 40/98] Updated on 2026-08-14 --- build.gradle.kts | 1 + core/datasource/build.gradle.kts | 8 + .../1.json | 304 ++++++++++++++++++ .../datasource/local/db/TangemDatabase.kt | 30 ++ .../db/dao/CryptoCurrenciesAccountDao.kt | 25 ++ .../local/db/dao/CryptoCurrencyDao.kt | 39 +++ .../datasource/local/db/dao/UserWalletDao.kt | 30 ++ .../local/db/di/TangemDatabaseModule.kt | 28 ++ .../entity/CryptoCurrenciesAccountEntity.kt | 30 ++ .../local/db/entity/CryptoCurrencyEntity.kt | 42 +++ .../local/db/entity/UserWalletEntity.kt | 22 ++ .../datasource/local/db/utils/Converters.kt | 27 ++ gradle/dependencies.toml | 5 + 13 files changed, 591 insertions(+) create mode 100644 core/datasource/schemas/com.tangem.datasource.local.db.TangemDatabase/1.json create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/db/TangemDatabase.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/db/dao/CryptoCurrenciesAccountDao.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/db/dao/CryptoCurrencyDao.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/db/dao/UserWalletDao.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/db/di/TangemDatabaseModule.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/db/entity/CryptoCurrenciesAccountEntity.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/db/entity/CryptoCurrencyEntity.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/db/entity/UserWalletEntity.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/db/utils/Converters.kt diff --git a/build.gradle.kts b/build.gradle.kts index 8ceb8869c1..d92b770060 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,6 +8,7 @@ plugins { alias(deps.plugins.hilt.android) apply false alias(deps.plugins.google.services) apply false alias(deps.plugins.firebase.crashlytics) apply false + alias(deps.plugins.room) apply false } val clean by tasks.registering { diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 06a4d5180e..26d30378a2 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -3,11 +3,16 @@ plugins { alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.kapt) alias(deps.plugins.hilt.android) + alias(deps.plugins.room) id("configuration") } android { namespace = "com.tangem.datasource" + + room { + schemaDirectory("$projectDir/schemas") + } } dependencies { @@ -61,4 +66,7 @@ dependencies { /** Local storages */ implementation(deps.androidx.datastore) + implementation(deps.room.runtime) + implementation(deps.room.ktx) + kapt(deps.room.compiler) } \ No newline at end of file diff --git a/core/datasource/schemas/com.tangem.datasource.local.db.TangemDatabase/1.json b/core/datasource/schemas/com.tangem.datasource.local.db.TangemDatabase/1.json new file mode 100644 index 0000000000..3efb2a6a01 --- /dev/null +++ b/core/datasource/schemas/com.tangem.datasource.local.db.TangemDatabase/1.json @@ -0,0 +1,304 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "a8a710af25033ee27e5043d001385234", + "entities": [ + { + "tableName": "UserWalletEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `artworkUrl` TEXT NOT NULL, `isMultiCurrency` INTEGER NOT NULL, `hasBackupError` INTEGER NOT NULL, `cardsInWallet` TEXT NOT NULL, `ordinalNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "artworkUrl", + "columnName": "artworkUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isMultiCurrency", + "columnName": "isMultiCurrency", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasBackupError", + "columnName": "hasBackupError", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cardsInWallet", + "columnName": "cardsInWallet", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ordinalNumber", + "columnName": "ordinalNumber", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_UserWalletEntity_id", + "unique": true, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_UserWalletEntity_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_UserWalletEntity_ordinalNumber", + "unique": true, + "columnNames": [ + "ordinalNumber" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_UserWalletEntity_ordinalNumber` ON `${TABLE_NAME}` (`ordinalNumber`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "CryptoCurrenciesAccountEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `userWalletId` TEXT NOT NULL, `title` TEXT NOT NULL, `currenciesCount` INTEGER NOT NULL, `isArchived` INTEGER NOT NULL, `ordinalNumber` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`userWalletId`) REFERENCES `UserWalletEntity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userWalletId", + "columnName": "userWalletId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currenciesCount", + "columnName": "currenciesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isArchived", + "columnName": "isArchived", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ordinalNumber", + "columnName": "ordinalNumber", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_CryptoCurrenciesAccountEntity_id", + "unique": false, + "columnNames": [ + "id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_CryptoCurrenciesAccountEntity_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_CryptoCurrenciesAccountEntity_userWalletId", + "unique": false, + "columnNames": [ + "userWalletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_CryptoCurrenciesAccountEntity_userWalletId` ON `${TABLE_NAME}` (`userWalletId`)" + } + ], + "foreignKeys": [ + { + "table": "UserWalletEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "userWalletId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "CryptoCurrencyEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `currencyBackendId` TEXT, `networkId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `userWalletId` TEXT NOT NULL, `name` TEXT NOT NULL, `symbol` TEXT NOT NULL, `decimals` INTEGER NOT NULL, `contractAddress` TEXT, `derivationPath` TEXT, FOREIGN KEY(`accountId`) REFERENCES `CryptoCurrenciesAccountEntity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`userWalletId`) REFERENCES `UserWalletEntity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "currencyBackendId", + "columnName": "currencyBackendId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "networkId", + "columnName": "networkId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userWalletId", + "columnName": "userWalletId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "decimals", + "columnName": "decimals", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contractAddress", + "columnName": "contractAddress", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "derivationPath", + "columnName": "derivationPath", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_CryptoCurrencyEntity_currencyBackendId", + "unique": false, + "columnNames": [ + "currencyBackendId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_CryptoCurrencyEntity_currencyBackendId` ON `${TABLE_NAME}` (`currencyBackendId`)" + }, + { + "name": "index_CryptoCurrencyEntity_networkId", + "unique": false, + "columnNames": [ + "networkId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_CryptoCurrencyEntity_networkId` ON `${TABLE_NAME}` (`networkId`)" + }, + { + "name": "index_CryptoCurrencyEntity_accountId", + "unique": false, + "columnNames": [ + "accountId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_CryptoCurrencyEntity_accountId` ON `${TABLE_NAME}` (`accountId`)" + }, + { + "name": "index_CryptoCurrencyEntity_userWalletId", + "unique": false, + "columnNames": [ + "userWalletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_CryptoCurrencyEntity_userWalletId` ON `${TABLE_NAME}` (`userWalletId`)" + } + ], + "foreignKeys": [ + { + "table": "CryptoCurrenciesAccountEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "accountId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "UserWalletEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "userWalletId" + ], + "referencedColumns": [ + "id" + ] + } + ] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a8a710af25033ee27e5043d001385234')" + ] + } +} diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/db/TangemDatabase.kt b/core/datasource/src/main/java/com/tangem/datasource/local/db/TangemDatabase.kt new file mode 100644 index 0000000000..7961908686 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/db/TangemDatabase.kt @@ -0,0 +1,30 @@ +package com.tangem.datasource.local.db + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.tangem.datasource.local.db.dao.CryptoCurrenciesAccountDao +import com.tangem.datasource.local.db.dao.CryptoCurrencyDao +import com.tangem.datasource.local.db.dao.UserWalletDao +import com.tangem.datasource.local.db.entity.CryptoCurrenciesAccountEntity +import com.tangem.datasource.local.db.entity.CryptoCurrencyEntity +import com.tangem.datasource.local.db.entity.UserWalletEntity +import com.tangem.datasource.local.db.utils.Converters + +@Database( + entities = [ + UserWalletEntity::class, + CryptoCurrenciesAccountEntity::class, + CryptoCurrencyEntity::class, + ], + version = 1, +) +@TypeConverters(Converters::class) +abstract class TangemDatabase : RoomDatabase() { + + abstract fun userWalletDao(): UserWalletDao + + abstract fun cryptoCurrencyDao(): CryptoCurrencyDao + + abstract fun cryptoCurrenciesAccountDao(): CryptoCurrenciesAccountDao +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/db/dao/CryptoCurrenciesAccountDao.kt b/core/datasource/src/main/java/com/tangem/datasource/local/db/dao/CryptoCurrenciesAccountDao.kt new file mode 100644 index 0000000000..92cbe0b2fc --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/db/dao/CryptoCurrenciesAccountDao.kt @@ -0,0 +1,25 @@ +package com.tangem.datasource.local.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import com.tangem.datasource.local.db.entity.CryptoCurrenciesAccountEntity +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +@Dao +interface CryptoCurrenciesAccountDao { + + @Insert + suspend fun insert(account: CryptoCurrenciesAccountEntity) + + @Update + suspend fun update(account: CryptoCurrenciesAccountEntity) + + @Query("SELECT * FROM CryptoCurrenciesAccountEntity WHERE userWalletId = :userWalletId") + fun observeByUserWalletId(userWalletId: UserWalletId): Flow> + + @Query("SELECT * FROM CryptoCurrenciesAccountEntity WHERE userWalletId = :userWalletId") + suspend fun selectByUserWalletId(userWalletId: UserWalletId): List +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/db/dao/CryptoCurrencyDao.kt b/core/datasource/src/main/java/com/tangem/datasource/local/db/dao/CryptoCurrencyDao.kt new file mode 100644 index 0000000000..a7cbfec3cf --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/db/dao/CryptoCurrencyDao.kt @@ -0,0 +1,39 @@ +package com.tangem.datasource.local.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import com.tangem.datasource.local.db.entity.CryptoCurrencyEntity +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +@Dao +interface CryptoCurrencyDao { + + @Insert + suspend fun insert(currencies: List) + + @Query( + """ + SELECT * FROM CryptoCurrencyEntity + WHERE userWalletId = :userWalletId AND accountId = :accountId + """, + ) + suspend fun selectByAccountId(userWalletId: UserWalletId, accountId: Int): List + + @Query( + """ + SELECT * FROM CryptoCurrencyEntity + WHERE userWalletId = :userWalletId AND accountId = :accountId + """, + ) + fun observeByAccountId(userWalletId: UserWalletId, accountId: Int): Flow> + + @Query( + """ + SELECT COUNT(*) FROM CryptoCurrencyEntity + WHERE userWalletId = :userWalletId + """, + ) + suspend fun countByUserWalletId(userWalletId: UserWalletId): Int +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/db/dao/UserWalletDao.kt b/core/datasource/src/main/java/com/tangem/datasource/local/db/dao/UserWalletDao.kt new file mode 100644 index 0000000000..31bca725e8 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/db/dao/UserWalletDao.kt @@ -0,0 +1,30 @@ +package com.tangem.datasource.local.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import com.tangem.datasource.local.db.entity.UserWalletEntity +import kotlinx.coroutines.flow.Flow + +@Dao +interface UserWalletDao { + + @Insert + suspend fun insert(vararg wallets: UserWalletEntity) + + @Update + suspend fun update(wallet: UserWalletEntity) + + @Query("SELECT * FROM UserWalletEntity") + suspend fun selectAll(): List + + @Query("SELECT * FROM UserWalletEntity") + fun observeAll(): Flow> + + @Query("SELECT * FROM UserWalletEntity WHERE id = :id") + suspend fun selectById(id: String): UserWalletEntity? + + @Query("SELECT * FROM UserWalletEntity WHERE id = :id") + fun observeById(id: String): Flow +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/db/di/TangemDatabaseModule.kt b/core/datasource/src/main/java/com/tangem/datasource/local/db/di/TangemDatabaseModule.kt new file mode 100644 index 0000000000..9b52c19cb5 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/db/di/TangemDatabaseModule.kt @@ -0,0 +1,28 @@ +package com.tangem.datasource.local.db.di + +import android.content.Context +import androidx.room.Room +import com.tangem.datasource.local.db.TangemDatabase +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object TangemDatabaseModule { + + private const val DATABASE_NAME = "tangem_database.db" + + @Provides + @Singleton + fun provideDatabase(@ApplicationContext context: Context): TangemDatabase { + return Room.databaseBuilder( + context, + TangemDatabase::class.java, + DATABASE_NAME, + ).build() + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/db/entity/CryptoCurrenciesAccountEntity.kt b/core/datasource/src/main/java/com/tangem/datasource/local/db/entity/CryptoCurrenciesAccountEntity.kt new file mode 100644 index 0000000000..d9c940359a --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/db/entity/CryptoCurrenciesAccountEntity.kt @@ -0,0 +1,30 @@ +package com.tangem.datasource.local.db.entity + +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + foreignKeys = [ + ForeignKey( + entity = UserWalletEntity::class, + parentColumns = ["id"], + childColumns = ["userWalletId"], + onDelete = ForeignKey.CASCADE, + ), + ], + indices = [ + Index(value = ["id"]), + Index(value = ["userWalletId"]), + ], +) +data class CryptoCurrenciesAccountEntity( + @PrimaryKey + val id: Int, + val userWalletId: String, + val title: String, + val currenciesCount: Int, + val isArchived: Boolean, + val ordinalNumber: Int, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/db/entity/CryptoCurrencyEntity.kt b/core/datasource/src/main/java/com/tangem/datasource/local/db/entity/CryptoCurrencyEntity.kt new file mode 100644 index 0000000000..5aab1dcf2f --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/db/entity/CryptoCurrencyEntity.kt @@ -0,0 +1,42 @@ +package com.tangem.datasource.local.db.entity + +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + foreignKeys = [ + ForeignKey( + entity = CryptoCurrenciesAccountEntity::class, + parentColumns = ["id"], + childColumns = ["accountId"], + onDelete = ForeignKey.CASCADE, + ), + ForeignKey( + entity = UserWalletEntity::class, + parentColumns = ["id"], + childColumns = ["userWalletId"], + onDelete = ForeignKey.CASCADE, + ), + ], + indices = [ + Index(value = ["currencyBackendId"]), + Index(value = ["networkId"]), + Index(value = ["accountId"]), + Index(value = ["userWalletId"]), + ], +) +data class CryptoCurrencyEntity( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val currencyBackendId: String?, + val networkId: String, + val accountId: Int, + val userWalletId: String, + val name: String, + val symbol: String, + val decimals: Int, + val contractAddress: String?, + val derivationPath: String?, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/db/entity/UserWalletEntity.kt b/core/datasource/src/main/java/com/tangem/datasource/local/db/entity/UserWalletEntity.kt new file mode 100644 index 0000000000..9f07d4a325 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/db/entity/UserWalletEntity.kt @@ -0,0 +1,22 @@ +package com.tangem.datasource.local.db.entity + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + indices = [ + Index(value = ["id"], unique = true), + Index(value = ["ordinalNumber"], unique = true), + ], +) +data class UserWalletEntity( + @PrimaryKey + val id: String, + val name: String, + val artworkUrl: String, + val isMultiCurrency: Boolean, + val hasBackupError: Boolean, + val cardsInWallet: Set, + val ordinalNumber: Int, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/db/utils/Converters.kt b/core/datasource/src/main/java/com/tangem/datasource/local/db/utils/Converters.kt new file mode 100644 index 0000000000..47c939a20e --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/db/utils/Converters.kt @@ -0,0 +1,27 @@ +package com.tangem.datasource.local.db.utils + +import androidx.room.TypeConverter +import com.tangem.domain.wallets.models.UserWalletId + +internal class Converters { + + @TypeConverter + fun setFromString(value: String): Set { + return value.split(",").toSet() + } + + @TypeConverter + fun setToString(set: Set): String { + return set.joinToString(",") + } + + @TypeConverter + fun userWalletIdFromString(value: String): UserWalletId { + return UserWalletId(value) + } + + @TypeConverter + fun userWalletIdToString(userWalletId: UserWalletId): String { + return userWalletId.stringValue + } +} \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 15baa2f024..568622b970 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -82,6 +82,7 @@ spr-client = "3.6.2" web3j = "4.10.1" leakcanary = "2.13" decompose = "2.2.2" +room = "2.6.1" # endregion Other libraries # region Tangem @@ -120,6 +121,7 @@ firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = " google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" } hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } +room = { id = "androidx.room", version.ref = "room" } [libraries] # region Classpath @@ -253,4 +255,7 @@ web3j-core = { module = "org.web3j:core", version.ref = "web3j" } leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" } decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" } decompose-ext-compose = { module = "com.arkivanov.decompose:extensions-compose-jetpack", version.ref = "decompose" } +room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } +room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } +room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } # endregion Other From 8d96d165331e2303b3e2e4c7c24f57966648e519 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 18:01:03 +0300 Subject: [PATCH 41/98] Updated on 2026-08-14 --- .../api/tangemTech/TangemTechApi.kt | 44 +++++++ .../api/tangemTech/TangemTechApiV2.kt | 23 ++++ .../models/CreateUserTokensAccountBody.kt | 10 ++ .../models/UpdateUserTokensAccountBody.kt | 8 ++ .../models/UserTokensAccountResponse.kt | 16 +++ .../models/UserTokensSettingsResponse.kt | 10 ++ .../models/v2/UserTokensResponseV2.kt | 23 ++++ .../com/tangem/datasource/di/NetworkModule.kt | 115 ++++++++++-------- 8 files changed, 199 insertions(+), 50 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApiV2.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CreateUserTokensAccountBody.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UpdateUserTokensAccountBody.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensAccountResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensSettingsResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/v2/UserTokensResponseV2.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index 63e632438e..0a5dcf3fd9 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -76,10 +76,54 @@ interface TangemTechApi { @GET("promotion") suspend fun getPromotionInfo(@Query("programName") name: String): ApiResponse + @GET("settings/{wallet_id}") + suspend fun getUserTokensSettings( + @Header("card_public_key") cardPublicKey: String, + @Header("card_id") cardId: String, + @Path("wallet_id") walletId: String, + ): ApiResponse + + @PUT("settings/{wallet_id}") + suspend fun saveUserTokensSettings( + @Header("card_public_key") cardPublicKey: String, + @Header("card_id") cardId: String, + @Path("wallet_id") walletId: String, + @Body userTokensSettings: UserTokensSettingsResponse, + ): ApiResponse + @POST("user-network-account") suspend fun createUserNetworkAccount( @Header("card_public_key") cardPublicKey: String, @Header("card_id") cardId: String, @Body body: CreateUserNetworkAccountBody, ): ApiResponse + + @POST("account") + suspend fun createUserTokensAccount( + @Header("card_public_key") cardPublicKey: String, + @Header("card_id") cardId: String, + @Body body: CreateUserTokensAccountBody, + ): ApiResponse + + @PUT("account/{account_id}") + suspend fun updateUserTokensAccount( + @Header("card_public_key") cardPublicKey: String, + @Header("card_id") cardId: String, + @Path("account_id") accountId: Int, + @Body body: UpdateUserTokensAccountBody, + ): ApiResponse + + @PUT("account/{account_id}/archive") + suspend fun archiveUserTokensAccount( + @Header("card_public_key") cardPublicKey: String, + @Header("card_id") cardId: String, + @Path("account_id") accountId: Int, + ): ApiResponse + + @PUT("account/{account_id}/unarchive") + suspend fun restoreUserTokensAccount( + @Header("card_public_key") cardPublicKey: String, + @Header("card_id") cardId: String, + @Path("account_id") accountId: Int, + ): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApiV2.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApiV2.kt new file mode 100644 index 0000000000..80db96faa7 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApiV2.kt @@ -0,0 +1,23 @@ +package com.tangem.datasource.api.tangemTech + +import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.datasource.api.tangemTech.models.v2.UserTokensResponseV2 +import retrofit2.http.* + +interface TangemTechApiV2 { + + @GET("user-tokens/{wallet_id}") + suspend fun getUserTokens( + @Header("card_public_key") cardPublicKey: String, + @Header("card_id") cardId: String, + @Path("wallet_id") walletId: String, + ): ApiResponse + + @PUT("user-tokens/{wallet_id}") + suspend fun saveUserTokens( + @Header("card_public_key") cardPublicKey: String, + @Header("card_id") cardId: String, + @Path("wallet_id") walletId: String, + @Body userTokens: UserTokensResponseV2, + ): ApiResponse +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CreateUserTokensAccountBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CreateUserTokensAccountBody.kt new file mode 100644 index 0000000000..7f88539dad --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/CreateUserTokensAccountBody.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +data class CreateUserTokensAccountBody( + @Json(name = "accountId") + val id: Int, + @Json(name = "accountTitle") + val title: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UpdateUserTokensAccountBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UpdateUserTokensAccountBody.kt new file mode 100644 index 0000000000..2413b85d2b --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UpdateUserTokensAccountBody.kt @@ -0,0 +1,8 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +data class UpdateUserTokensAccountBody( + @Json(name = "accountTitle") + val title: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensAccountResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensAccountResponse.kt new file mode 100644 index 0000000000..0e8341612d --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensAccountResponse.kt @@ -0,0 +1,16 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +data class UserTokensAccountResponse( + @Json(name = "accountId") + val id: Int, + @Json(name = "accountTitle") + val title: String, + @Json(name = "archived") + val isArchived: Boolean, + @Json(name = "tokensCount") + val tokensCount: Int? = null, + @Json(name = "tokens") + val tokens: List? = null, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensSettingsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensSettingsResponse.kt new file mode 100644 index 0000000000..9d5a0554cf --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensSettingsResponse.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +data class UserTokensSettingsResponse( + @Json(name = "group") + val group: UserTokensResponse.GroupType, + @Json(name = "sort") + val sort: UserTokensResponse.SortType, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/v2/UserTokensResponseV2.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/v2/UserTokensResponseV2.kt new file mode 100644 index 0000000000..fdb8a0df7c --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/v2/UserTokensResponseV2.kt @@ -0,0 +1,23 @@ +package com.tangem.datasource.api.tangemTech.models.v2 + +import com.squareup.moshi.Json +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse + +data class UserTokensResponseV2( + @Json(name = "accounts") + val accounts: List, +) { + + data class TokensAccount( + @Json(name = "id") + val id: Int, + @Json(name = "title") + val title: String, + @Json(name = "tokens") + val tokens: List? = null, + @Json(name = "tokensCount") + val tokensCount: Int? = null, + @Json(name = "archived") + val isArchived: Boolean, + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index d229d1dbf1..e687c394e9 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -6,6 +6,7 @@ import com.tangem.datasource.BuildConfig import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.TangemTechApiV2 import com.tangem.datasource.api.tangemTech.TangemTechServiceApi import com.tangem.datasource.utils.RequestHeader.* import com.tangem.datasource.utils.addHeaders @@ -57,45 +58,33 @@ class NetworkModule { @Provides @Singleton - fun provideTangemTechApi(@NetworkMoshi moshi: Moshi, @ApplicationContext context: Context): TangemTechApi { - return Retrofit.Builder() - .addConverterFactory(MoshiConverterFactory.create(moshi)) - .addCallAdapterFactory(ApiResponseCallAdapterFactory.create()) - .baseUrl(PROD_TANGEM_TECH_BASE_URL) - .client( - OkHttpClient.Builder() - .addHeaders( - CacheControlHeader, - // TODO("refactor header init") get auth data after biometric auth to avoid race condition - // AuthenticationHeader(authProvider), - ) - .addLoggers(context) - .build(), - ) - .build() - .create(TangemTechApi::class.java) + fun provideTangemTechApi( + @NetworkMoshi moshi: Moshi, + @ApplicationContext context: Context, + appVersionProvider: AppVersionProvider, + ): TangemTechApi { + return provideTangemTechApiInternal(moshi, context, appVersionProvider, PROD_V1_TANGEM_TECH_BASE_URL) + } + + @Provides + @Singleton + fun provideTangemTechApiV2( + @NetworkMoshi moshi: Moshi, + @ApplicationContext context: Context, + appVersionProvider: AppVersionProvider, + ): TangemTechApiV2 { + return provideTangemTechApiInternal(moshi, context, appVersionProvider, PROD_V2_TANGEM_TECH_BASE_URL) } @Provides @DevTangemApi @Singleton - fun provideTangemTechDevApi(@NetworkMoshi moshi: Moshi, @ApplicationContext context: Context): TangemTechApi { - return Retrofit.Builder() - .addConverterFactory(MoshiConverterFactory.create(moshi)) - .addCallAdapterFactory(ApiResponseCallAdapterFactory.create()) - .baseUrl(DEV_TANGEM_TECH_BASE_URL) - .client( - OkHttpClient.Builder() - .addHeaders( - CacheControlHeader, - // TODO("refactor header init") get auth data after biometric auth to avoid race condition - // AuthenticationHeader(authProvider), - ) - .addLoggers(context) - .build(), - ) - .build() - .create(TangemTechApi::class.java) + fun provideTangemTechDevApi( + @NetworkMoshi moshi: Moshi, + @ApplicationContext context: Context, + appVersionProvider: AppVersionProvider, + ): TangemTechApi { + return provideTangemTechApiInternal(moshi, context, appVersionProvider, DEV_V1_TANGEM_TECH_BASE_URL) } @Provides @@ -105,32 +94,58 @@ class NetworkModule { @ApplicationContext context: Context, appVersionProvider: AppVersionProvider, ): TangemTechServiceApi { + return provideTangemTechApiInternal( + moshi, + context, + appVersionProvider, + PROD_V1_TANGEM_TECH_BASE_URL, + timeoutSeconds = TANGEM_TECH_SERVICE_TIMEOUT_SECONDS, + ) + } + + private inline fun provideTangemTechApiInternal( + moshi: Moshi, + context: Context, + appVersionProvider: AppVersionProvider, + baseUrl: String, + timeoutSeconds: Long? = null, + ): T { + val client = OkHttpClient.Builder() + .let { builder -> + if (timeoutSeconds != null) { + builder.callTimeout(timeoutSeconds, TimeUnit.SECONDS) + } else { + builder + } + } + .addHeaders( + CacheControlHeader, + AppVersionPlatformHeaders(appVersionProvider), + // TODO("refactor header init") get auth data after biometric auth to avoid race condition + // AuthenticationHeader(authProvider), + ) + .addLoggers(context) + .build() + return Retrofit.Builder() .addConverterFactory(MoshiConverterFactory.create(moshi)) .addCallAdapterFactory(ApiResponseCallAdapterFactory.create()) - .baseUrl(PROD_TANGEM_TECH_BASE_URL) - .client( - OkHttpClient.Builder() - .callTimeout(timeout = 5, unit = TimeUnit.SECONDS) - .addHeaders( - CacheControlHeader, - AppVersionPlatformHeaders(appVersionProvider), - // TODO("refactor header init") get auth data after biometric auth to avoid race condition - // AuthenticationHeader(authProvider), - ) - .addLoggers(context) - .build(), - ) + .baseUrl(baseUrl) + .client(client) .build() - .create(TangemTechServiceApi::class.java) + .create(T::class.java) } private companion object { const val PROD_EXPRESS_BASE_URL = "https://express.tangem.com/v1/" const val DEV_EXPRESS_BASE_URL = "[REDACTED_ENV_URL]" - const val PROD_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/" - const val DEV_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/" + const val DEV_V1_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/" + + const val PROD_V1_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/" + const val PROD_V2_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v2/" + + const val TANGEM_TECH_SERVICE_TIMEOUT_SECONDS = 5L const val PAYMENTOLOGY_BASE_URL: String = "https://paymentologygate.oa.r.appspot.com/" const val API_ONE_INCH_TIMEOUT_MS = 5000L From c5ded61ae134362e62231b515290d9a439313e53 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 26 Apr 2024 12:36:52 +0300 Subject: [PATCH 42/98] Updated on 2026-08-14 --- app/src/debug/res/xml/network_security_config.xml | 4 ++-- app/src/external/res/xml/network_security_config.xml | 5 +++-- app/src/internal/res/xml/network_security_config.xml | 4 ++-- app/src/main/res/xml/network_security_config.xml | 7 ++----- app/src/mocked/res/xml/network_security_config.xml | 4 ++-- .../connection/RealInternetConnectionManager.kt | 10 +++++++++- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/app/src/debug/res/xml/network_security_config.xml b/app/src/debug/res/xml/network_security_config.xml index 894912ac61..52c44ac992 100644 --- a/app/src/debug/res/xml/network_security_config.xml +++ b/app/src/debug/res/xml/network_security_config.xml @@ -1,9 +1,9 @@ - + - + diff --git a/app/src/external/res/xml/network_security_config.xml b/app/src/external/res/xml/network_security_config.xml index 894912ac61..d49c0f70ba 100644 --- a/app/src/external/res/xml/network_security_config.xml +++ b/app/src/external/res/xml/network_security_config.xml @@ -1,9 +1,10 @@ - + - + + diff --git a/app/src/internal/res/xml/network_security_config.xml b/app/src/internal/res/xml/network_security_config.xml index 894912ac61..52c44ac992 100644 --- a/app/src/internal/res/xml/network_security_config.xml +++ b/app/src/internal/res/xml/network_security_config.xml @@ -1,9 +1,9 @@ - + - + diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 22e9f8653e..683208ffda 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -1,11 +1,8 @@ - - - api.tangem.com + - - + diff --git a/app/src/mocked/res/xml/network_security_config.xml b/app/src/mocked/res/xml/network_security_config.xml index 894912ac61..52c44ac992 100644 --- a/app/src/mocked/res/xml/network_security_config.xml +++ b/app/src/mocked/res/xml/network_security_config.xml @@ -1,9 +1,9 @@ - + - + diff --git a/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt b/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt index 8ca86a2587..7a3279d188 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.reactive.asFlow +private const val PING_SERVER = "https://clients3.google.com/generate_204" private const val PING_INTERVAL = 5_000 internal class RealInternetConnectionManager : NetworkConnectionManager { @@ -24,7 +25,12 @@ internal class RealInternetConnectionManager : NetworkConnectionManager { private val initialNetworkResult: Boolean by lazy { ReactiveNetwork - .checkInternetConnectivity() + .checkInternetConnectivity( + InternetObservingSettings.builder() + .host(PING_SERVER) + .port(443) + .build(), + ) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .blockingGet() @@ -33,6 +39,8 @@ internal class RealInternetConnectionManager : NetworkConnectionManager { override val isOnlineFlow: StateFlow = ReactiveNetwork .observeInternetConnectivity( InternetObservingSettings.builder() + .host(PING_SERVER) + .port(443) .interval(PING_INTERVAL) .build(), ) From 9bb78a305f621513eac25c64a005a2882dc32b18 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 15:41:52 +0200 Subject: [PATCH 43/98] Updated on 2026-08-14 --- .../tangem/domain/walletmanager/DefaultWalletManagersFacade.kt | 2 +- gradle/dependencies.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index cfbbd653ec..e060ac5043 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -59,7 +59,7 @@ class DefaultWalletManagersFacade( private val txHistoryStateConverter by lazy { SdkTransactionHistoryStateConverter() } private val txHistoryItemConverter by lazy { SdkTransactionHistoryItemConverter(assetReader, moshi) } private val sdkPageConverter by lazy { SdkPageConverter() } - private val estimationFeeAddressFactory by lazy { EstimationFeeAddressFactory(mnemonic) } + private val estimationFeeAddressFactory by lazy { EstimationFeeAddressFactory() } override suspend fun update( userWalletId: UserWalletId, diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 568622b970..40649092a8 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -86,7 +86,7 @@ room = "2.6.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-603" +tangemBlockchainSdk = "develop-609" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-346" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From d4ae36577c22eec96d156d4ae43d11039b7518cf Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 15:52:27 +0200 Subject: [PATCH 44/98] Updated on 2026-08-14 --- .../java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt | 2 -- .../tangem/domain/walletmanager/DefaultWalletManagersFacade.kt | 1 - 2 files changed, 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt index a24becfb81..19a38d53e6 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt @@ -26,7 +26,6 @@ internal object WalletManagersFacadeModule { userWalletsStore: UserWalletsStore, assetReader: AssetReader, @SdkMoshi moshi: Moshi, - mnemonicRepository: MnemonicRepository, blockchainSDKFactory: BlockchainSDKFactory, ): WalletManagersFacade { return DefaultWalletManagersFacade( @@ -34,7 +33,6 @@ internal object WalletManagersFacadeModule { userWalletsStore = userWalletsStore, assetReader = assetReader, moshi = moshi, - mnemonic = mnemonicRepository.generateDefaultMnemonic(), blockchainSDKFactory = blockchainSDKFactory, ) } diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index e060ac5043..42a25276bc 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -46,7 +46,6 @@ import java.util.EnumSet class DefaultWalletManagersFacade( private val walletManagersStore: WalletManagersStore, private val userWalletsStore: UserWalletsStore, - mnemonic: Mnemonic, assetReader: AssetReader, moshi: Moshi, blockchainSDKFactory: BlockchainSDKFactory, From c58110a78bc14d1ea2fa4aef7d51fb6ace630029 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Apr 2024 16:01:59 +0200 Subject: [PATCH 45/98] Updated on 2026-08-14 --- .../java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt | 1 - .../tangem/domain/walletmanager/DefaultWalletManagersFacade.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt index 19a38d53e6..2bd8c1d4c1 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt @@ -8,7 +8,6 @@ import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.domain.walletmanager.DefaultWalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.feature.onboarding.data.MnemonicRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 42a25276bc..1f8a476da0 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -17,7 +17,6 @@ import com.tangem.blockchain.common.txhistory.TransactionHistoryRequest import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult import com.tangem.blockchainsdk.BlockchainSDKFactory -import com.tangem.crypto.bip39.Mnemonic import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.datasource.asset.reader.AssetReader import com.tangem.datasource.local.userwallet.UserWalletsStore From 463e5b1d52bcf5981162fbe9da666e88a867f2da Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2024 21:17:41 +0100 Subject: [PATCH 46/98] Updated on 2026-08-14 --- .../com/tangem/datasource/api/express/TangemExpressApi.kt | 1 + .../java/com/tangem/feature/swap/DefaultSwapRepository.kt | 2 ++ .../com/tangem/feature/swap/domain/api/SwapRepository.kt | 1 + .../com/tangem/feature/swap/domain/SwapInteractorImpl.kt | 6 ++++-- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt index b7055574a3..76c7dce8bd 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt @@ -42,6 +42,7 @@ interface TangemExpressApi { @Query("fromContractAddress") fromContractAddress: String, @Query("fromNetwork") fromNetwork: String, @Query("toContractAddress") toContractAddress: String, + @Query("fromAddress") fromAddress: String, @Query("toNetwork") toNetwork: String, @Query("fromAmount") fromAmount: String, @Query("fromDecimals") fromDecimals: Int, diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt index 66ff152f20..68c61f2c71 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt @@ -249,6 +249,7 @@ internal class DefaultSwapRepository @Inject constructor( fromContractAddress: String, fromNetwork: String, toContractAddress: String, + fromAddress: String, toNetwork: String, fromAmount: String, fromDecimals: Int, @@ -266,6 +267,7 @@ internal class DefaultSwapRepository @Inject constructor( fromContractAddress = fromContractAddress, fromNetwork = fromNetwork, toContractAddress = toContractAddress, + fromAddress = fromAddress, toNetwork = toNetwork, fromAmount = fromAmount, fromDecimals = fromDecimals, diff --git a/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt b/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt index 0a04d8950f..6ae1d7e0d1 100644 --- a/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt +++ b/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt @@ -58,6 +58,7 @@ interface SwapRepository { fromContractAddress: String, fromNetwork: String, toContractAddress: String, + fromAddress: String, toNetwork: String, fromAmount: String, fromDecimals: Int, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 6ab50afce3..bea16b1e5c 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -583,13 +583,14 @@ internal class SwapInteractorImpl @Inject constructor( fromContractAddress = currencyToSend.currency.getContractAddress(), fromNetwork = currencyToSend.currency.network.backendId, toContractAddress = currencyToGet.currency.getContractAddress(), + fromAddress = currencyToSend.value.networkAddress?.defaultAddress?.value.orEmpty(), toNetwork = currencyToGet.currency.network.backendId, fromAmount = amount.toStringWithRightOffset(), fromDecimals = amount.decimals, toDecimals = currencyToGet.currency.decimals, providerId = swapProvider.providerId, rateType = RateType.FLOAT, - toAddress = currencyToGet.value.networkAddress?.defaultAddress?.value ?: "", + toAddress = currencyToGet.value.networkAddress?.defaultAddress?.value.orEmpty(), refundAddress = currencyToSend.value.networkAddress?.defaultAddress?.value, refundExtraId = null, // currently always null ).getOrElse { return SwapTransactionState.ExpressError(it) } @@ -1048,13 +1049,14 @@ internal class SwapInteractorImpl @Inject constructor( fromContractAddress = fromToken.currency.getContractAddress(), fromNetwork = fromToken.currency.network.backendId, toContractAddress = toToken.currency.getContractAddress(), + fromAddress = fromToken.value.networkAddress?.defaultAddress?.value.orEmpty(), toNetwork = toToken.currency.network.backendId, fromAmount = amount.toStringWithRightOffset(), fromDecimals = amount.decimals, toDecimals = toToken.currency.decimals, providerId = provider.providerId, rateType = RateType.FLOAT, - toAddress = toToken.value.networkAddress?.defaultAddress?.value ?: "", + toAddress = toToken.value.networkAddress?.defaultAddress?.value.orEmpty(), ).fold( ifRight = { swapData -> val feeData = transactionManager.getFee( From 2a61e32f0c8d752228343b1df7c897babbda2047 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 27 Apr 2024 00:26:35 +0800 Subject: [PATCH 47/98] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../datasource/asset/loader/AssetLoader.kt | 12 ++--- libs/blockchain-sdk/build.gradle.kts | 6 +++ .../BlockchainProvidersResponseLoader.kt | 48 +++++++++++++++++-- 4 files changed, 57 insertions(+), 11 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 367aa6465e..fdf0660901 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 367aa6465ec6ee7fdccce6cc7d7abeed5aad1111 +Subproject commit fdf0660901442ade296db42bb867fc5c16a14262 diff --git a/core/datasource/src/main/java/com/tangem/datasource/asset/loader/AssetLoader.kt b/core/datasource/src/main/java/com/tangem/datasource/asset/loader/AssetLoader.kt index e271679df4..042319e1f4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/asset/loader/AssetLoader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/asset/loader/AssetLoader.kt @@ -31,11 +31,11 @@ class AssetLoader @Inject constructor( } .fold( onSuccess = { parsedConfig -> - if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config is null")) + if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config [$fileName] is null")) parsedConfig }, onFailure = { - Timber.e(it, "Failed to load config from assets") + Timber.e(it, "Failed to load config [$fileName] from assets") null }, ) @@ -53,11 +53,11 @@ class AssetLoader @Inject constructor( } .fold( onSuccess = { parsedConfig -> - if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config is null")) + if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config [$fileName] is null")) parsedConfig.orEmpty() }, onFailure = { - Timber.e(it, "Failed to load config from assets") + Timber.e(it, "Failed to load config [$fileName] from assets") emptyList() }, ) @@ -75,11 +75,11 @@ class AssetLoader @Inject constructor( } .fold( onSuccess = { parsedConfig -> - if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config is null")) + if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config [$fileName] is null")) parsedConfig.orEmpty() }, onFailure = { - Timber.e(it, "Failed to load config from assets") + Timber.e(it, "Failed to load config [$fileName] from assets") emptyMap() }, ) diff --git a/libs/blockchain-sdk/build.gradle.kts b/libs/blockchain-sdk/build.gradle.kts index ecf2e9a795..88d96a0405 100644 --- a/libs/blockchain-sdk/build.gradle.kts +++ b/libs/blockchain-sdk/build.gradle.kts @@ -32,6 +32,12 @@ dependencies { implementation(deps.timber) // endregion + // region Firebase libraries + implementation(platform(deps.firebase.bom)) + implementation(deps.firebase.analytics) + implementation(deps.firebase.crashlytics) + // endregion + // region Tangem libraries implementation(deps.tangem.blockchain) { exclude(module = "joda-time") } implementation(deps.tangem.card.core) diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/BlockchainProvidersResponseLoader.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/BlockchainProvidersResponseLoader.kt index 7613213ba4..310ec39f9a 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/BlockchainProvidersResponseLoader.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/BlockchainProvidersResponseLoader.kt @@ -1,5 +1,6 @@ package com.tangem.blockchainsdk.loader +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchainsdk.BlockchainProvidersResponse import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.tangemTech.TangemTechServiceApi @@ -28,18 +29,26 @@ internal class BlockchainProvidersResponseLoader @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, ) { + private val firebaseCrashlytics by lazy(FirebaseCrashlytics::getInstance) + /** Load [BlockchainProvidersResponse] */ suspend fun load(): BlockchainProvidersResponse? { + val localResponse = loadLocal() ?: return null + return runCatching(dispatcher = dispatchers.io, block = ::loadRemote) .fold( - onSuccess = { it }, + onSuccess = { remoteResponse -> mergeResponses(local = localResponse, remote = remoteResponse) }, onFailure = { Timber.e(it, "Failed to load blockchain provider types from backend") - loadLocal() + localResponse }, ) } + private suspend fun loadLocal(): BlockchainProvidersResponse? { + return assetLoader.load(fileName = PROVIDER_TYPES_FILE_NAME) + } + private suspend fun loadRemote(): BlockchainProvidersResponse { return tangemTechServiceApi.getBlockchainProviders( cardPublicKey = authProvider.getCardPublicKey(), @@ -47,8 +56,39 @@ internal class BlockchainProvidersResponseLoader @Inject constructor( ) } - private suspend fun loadLocal(): BlockchainProvidersResponse? { - return assetLoader.load(fileName = PROVIDER_TYPES_FILE_NAME) + /** Merge blockchains with non-empty providers [remote] from remote with blockchains from local [local] */ + private fun mergeResponses( + local: BlockchainProvidersResponse, + remote: BlockchainProvidersResponse, + ): BlockchainProvidersResponse { + /* + * Example: + * val remote = mapOf("a" to 1, "b" to 2, "c" to 3) + * val local = mapOf("a" to 11, "e" to 4, "f" to 5) + * + * local + remote // { a = 1, e = 4, f = 5, b = 2, c = 3 } + */ + val result = local + remote.filterValues { it.isNotEmpty() } + + if (result != remote) { + val missingBlockchains = result.keys - remote.keys + val blockchainsWithoutProviders = remote.filterValues { it.isEmpty() }.keys + + recordException(missingBlockchains = missingBlockchains + blockchainsWithoutProviders) + } + + return result + } + + private fun recordException(missingBlockchains: Set) { + val exception = IllegalStateException( + "Remote config does not contain required blockchains or providers information: " + + missingBlockchains.joinToString(), + ) + + Timber.e(exception) + + firebaseCrashlytics.recordException(exception) } private companion object { From 31bfe75e147dca904658bcd38e996ef50d5c7018 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 May 2024 12:01:36 +0200 Subject: [PATCH 48/98] Updated on 2026-08-14 --- .../com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt index 1f4b9e650e..bc672b7c2f 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt @@ -31,14 +31,12 @@ internal class WelcomeViewModel @Inject constructor( private val stateInternal = MutableStateFlow(WelcomeScreenState()) val state: StateFlow = stateInternal - init { + override fun onCreate(owner: LifecycleOwner) { store.dispatch(WelcomeAction.SetCoroutineScope(viewModelScope)) subscribeToStoreChanges() initGlobalState() - } - override fun onCreate(owner: LifecycleOwner) { val welcomeAction = if (initialIntent != null) { WelcomeAction.ProceedWithIntent(initialIntent) } else { @@ -81,9 +79,10 @@ internal class WelcomeViewModel @Inject constructor( } } - override fun onCleared() { + override fun onDestroy(owner: LifecycleOwner) { store.dispatch(WelcomeAction.ClearCoroutineScope) store.unsubscribe(this) + super.onDestroy(owner) } private fun createWarningIfNeeded(error: TangemError?): WarningModel? { From 27b71e7def9501eaec3b45eabb3018809f4a3c22 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 5 May 2024 21:35:53 +0100 Subject: [PATCH 49/98] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index fdf0660901..07cfce37ff 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit fdf0660901442ade296db42bb867fc5c16a14262 +Subproject commit 07cfce37ff84e70081aca82d9948acb13c5c07b0 From af862e7ef1bf2074ef2680095d77bef4c24663a1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 5 May 2024 22:00:47 +0100 Subject: [PATCH 50/98] Updated on 2026-08-14 --- .../impl/data/TangemApiTokensPagingSource.kt | 2 +- .../state/confirm/SendNotificationFactory.kt | 24 ------------------- .../presentation/state/fee/FeeCalculation.kt | 1 - .../presentation/ui/amount/AmountField.kt | 1 - .../presentation/viewmodel/SendViewModel.kt | 1 - 5 files changed, 1 insertion(+), 28 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt index f9390fc4a2..dcacf9fef2 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt @@ -3,10 +3,10 @@ package com.tangem.tap.features.tokens.impl.data import androidx.paging.PagingSource import androidx.paging.PagingState import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.isSupportedInApp import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.domain.common.extensions.isSupportedInApp import com.tangem.domain.common.extensions.supportedBlockchains import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 0cdc5ba539..baa881f4d6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -10,8 +10,6 @@ import com.tangem.core.ui.extensions.networkIconResId import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.domain.common.extensions.minimalAmount import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -250,13 +248,6 @@ internal class SendNotificationFactory( } } - private fun MutableList.addFeeCoverageNotification(sendingAmount: Boolean) { - if (sendingAmount) { - analyticsEventHandler.send(SendAnalyticEvents.NoticeFeeCoverage) - add(SendNotification.Warning.FeeCoverageNotification) - } - } - private fun MutableList.addHighFeeWarningNotification( sendAmount: BigDecimal, ignoreAmountReduce: Boolean, @@ -284,21 +275,6 @@ internal class SendNotificationFactory( } } - // todo remove in [REDACTED_TASK_KEY] - private fun MutableList.addMinimumAmountErrorNotification( - feeAmount: BigDecimal, - receivedAmount: BigDecimal, - ) { - val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider() - val minimum = BigDecimal(DOGECOIN_MINIMUM) - - val isDogecoin = isDogecoin(coinCryptoCurrencyStatus.currency.network.id.value) - val isExceedDustLimit = checkDustLimits(feeAmount, receivedAmount, minimum) - if (isDogecoin && isExceedDustLimit) { - add(SendNotification.Error.MinimumAmountError(DOGECOIN_MINIMUM)) - } - } - private suspend fun MutableList.addDustWarningNotification( feeAmount: BigDecimal, receivedAmount: BigDecimal, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt index c0be8a0756..b06d42a23d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt @@ -1,6 +1,5 @@ package com.tangem.features.send.impl.presentation.state.fee -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.extensions.isZero import com.tangem.core.ui.utils.parseBigDecimal diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt index c77f28309b..8f7a69c8c1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt @@ -23,7 +23,6 @@ import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.rememberDecimalFormat import com.tangem.features.send.impl.presentation.state.fields.SendTextField import kotlinx.coroutines.delay -import kotlinx.coroutines.job @Composable internal fun AmountField(sendField: SendTextField.AmountField, appCurrencyCode: String) { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 72e1de02e6..5410b54f17 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -557,7 +557,6 @@ internal class SendViewModel @Inject constructor( ) return true } - val feeSelectorState = uiState.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false return checkIfFeeTooHigh( feeSelectorState = feeSelectorState, onShow = { diff -> From fa7a381e04cd1f66163cae92fe8a4fdf7cf0e7a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 27 Apr 2024 15:35:08 +0300 Subject: [PATCH 51/98] Updated on 2026-08-14 --- .../presentation/ui/AddCustomTokenContent.kt | 3 +- .../presentation/ui/AddCustomTokenScreen.kt | 18 +--- .../ui/AddCustomTokenTestContent.kt | 3 +- .../AddCustomTokenFloatingButton.kt | 3 +- .../ui/components/AddCustomTokenForm.kt | 3 +- .../ui/components/AddCustomTokenToolbar.kt | 3 +- .../ui/components/AddCustomTokenWarnings.kt | 15 +-- .../appcurrency/AppCurrencySelectorScreen.kt | 17 +--- .../ui/appsettings/AppSettingsScreen.kt | 17 +--- .../components/SettingsAlertDialog.kt | 16 +--- .../components/SettingsButtonItem.kt | 15 +-- .../components/SettingsCardItem.kt | 15 +-- .../components/SettingsSelectorDialog.kt | 16 +--- .../components/SettingsSwitchItem.kt | 15 +-- .../ui/cardsettings/CardSettingsScreen.kt | 15 +-- .../details/ui/details/DetailsScreen.kt | 17 +--- .../details/ui/resetcard/ResetCardScreen.kt | 15 +-- .../features/home/compose/StoriesScreen.kt | 3 +- .../home/compose/views/HomeButtons.kt | 3 +- .../compose/views/SearchCurrenciesButton.kt | 3 +- .../ui/components/ModalNotificationScreen.kt | 23 +---- .../EnrollBiometricsDialogConent.kt | 15 +-- .../ui/components/SaveWalletScreenContent.kt | 15 +-- .../presentation/ui/DetailedNetworksList.kt | 36 ++----- .../impl/presentation/ui/NetworkItemArrow.kt | 23 +---- .../tokens/impl/presentation/ui/TokenItem.kt | 28 ++---- .../impl/presentation/ui/TokensListScreen.kt | 17 +--- .../impl/presentation/ui/TokensListToolbar.kt | 7 +- .../welcome/ui/components/WarningDialog.kt | 55 +++-------- .../ui/components/WelcomeScreenContent.kt | 15 +-- .../com/tangem/core/ui/components/Buttons.kt | 42 +++----- .../core/ui/components/ButtonsDeprecated.kt | 74 +++------------ .../com/tangem/core/ui/components/Cards.kt | 29 ++---- .../ui/components/CurrencyPlaceholderIcon.kt | 16 +--- .../com/tangem/core/ui/components/Dialogs.kt | 78 ++++----------- .../com/tangem/core/ui/components/Notifier.kt | 15 +-- .../com/tangem/core/ui/components/Shimmers.kt | 43 ++++----- .../tangem/core/ui/components/TextFields.kt | 15 +-- .../com/tangem/core/ui/components/Warnings.kt | 57 +++++------ .../appbar/AppBarWithAdditionalButtons.kt | 63 +++--------- .../components/appbar/AppBarWithBackButton.kt | 15 +-- .../appbar/AppBarWithBackButtonAndIcon.kt | 20 +--- .../AppBarWithBackButtonAndIconContent.kt | 32 +------ .../ui/components/appbar/AppBarWithSearch.kt | 5 +- .../tangem/core/ui/components/atoms/Hand.kt | 15 +-- .../ui/components/atoms/text/EllipsisText.kt | 3 +- .../buttons/HorizontalActionChips.kt | 17 +--- .../core/ui/components/buttons/SmallButton.kt | 15 +-- .../ui/components/buttons/actions/Actions.kt | 28 ++---- .../segmentedbutton/SegmentedButton.kt | 25 +---- .../ui/components/fields/AmountTextField.kt | 3 +- .../components/icons/identicon/IdentIcon.kt | 20 +--- .../ui/components/inputrow/InputRowApprox.kt | 64 ++++++------- .../components/inputrow/InputRowBestRate.kt | 26 +---- .../ui/components/inputrow/InputRowDefault.kt | 23 +---- .../ui/components/inputrow/InputRowEnter.kt | 24 +---- .../components/inputrow/InputRowEnterInfo.kt | 24 +---- .../ui/components/inputrow/InputRowImage.kt | 27 +----- .../components/inputrow/InputRowRecipient.kt | 28 +----- .../inputrow/InputRowRecipientDefault.kt | 20 +--- .../components/inputrow/inner/PasteButton.kt | 3 +- .../marketprice/MarketPriceBlock.kt | 18 +--- .../components/notifications/Notification.kt | 17 +--- .../NotificationWithBackground.kt | 17 +--- .../core/ui/components/rows/ActionRow.kt | 5 +- .../ui/components/rows/SelectorRowItem.kt | 23 +---- .../ui/components/transactions/Transaction.kt | 19 +--- .../transactions/TransactionDoneTitle.kt | 21 +--- .../transactions/TxHistoryGroupTitle.kt | 17 +--- .../components/transactions/TxHistoryTitle.kt | 15 +-- .../empty/EmptyTransactionBlock.kt | 17 +--- .../tangem/core/ui/res/TangemThemePreview.kt | 21 ++++ .../addcustomtoken/ui/AddCustomTokenScreen.kt | 15 +-- .../ui/ChooseDerivationScreen.kt | 15 +-- .../ui/ChooseNetworkCustomScreen.kt | 15 +-- .../common/ui/ChooseWalletScreen.kt | 17 +--- .../common/ui/components/NetworkItem.kt | 15 +-- .../ui/components/SimpleSelectionBlock.kt | 15 +-- .../managetokens/ui/ChooseNetworkScreen.kt | 18 +--- .../managetokens/ui/ManageTokensScreen.kt | 18 +--- .../ui/components/AddCustomTokenButton.kt | 15 +-- .../ui/components/DerivationNotification.kt | 15 +-- .../ui/components/PriceChangesChart.kt | 7 +- .../managetokens/ui/components/SearchBar.kt | 15 +-- .../managetokens/ui/components/TokenButton.kt | 15 +-- .../ui/components/TokenRowItem.kt | 15 +-- .../wallet2/ui/ImportSeedPhraseScreen.kt | 25 ++--- .../wallet2/ui/PassphraseInfoBottomSheet.kt | 3 +- .../wallet2/ui/YourSeedPhraseScreen.kt | 5 +- .../ui/AgreementBottomSheetContent.kt | 15 +-- .../feature/referral/ui/AgreementText.kt | 17 +--- .../tangem/feature/referral/ui/AwardItems.kt | 5 +- .../referral/ui/NonParticipateBottomBlock.kt | 17 +--- .../referral/ui/ParticipateBottomBlock.kt | 45 ++------- .../feature/referral/ui/ReferralScreen.kt | 95 +++---------------- .../ui/amount/SendAmountContent.kt | 21 +--- .../presentation/ui/fee/SendSpeedSelector.kt | 17 +--- .../ui/recipient/ListItemWithIcon.kt | 24 +---- .../impl/presentation/ui/send/AmountBlock.kt | 24 +---- .../impl/presentation/ui/send/FeeBlock.kt | 19 +--- .../presentation/ui/send/RecipientBlock.kt | 22 +---- .../feature/swap/ui/ChooseFeeBottomSheet.kt | 5 +- .../swap/ui/ChooseProviderBottomSheet.kt | 3 +- .../com/tangem/feature/swap/ui/FeeItem.kt | 5 +- .../tangem/feature/swap/ui/ProviderItem.kt | 21 +--- .../swap/ui/SwapPermissionBottomSheet.kt | 15 +-- .../feature/swap/ui/SwapScreenContent.kt | 5 +- .../feature/swap/ui/SwapSelectTokenScreen.kt | 5 +- .../feature/swap/ui/SwapSuccessScreen.kt | 16 +--- .../tangem/feature/swap/ui/TransactionCard.kt | 13 +-- .../feature/swap/ui/WebViewBottomSheet.kt | 19 +--- .../actions/TesterActionsScreen.kt | 15 +-- .../featuretoggles/ui/FeatureTogglesScreen.kt | 24 +---- .../presentation/menu/ui/TesterMenuScreen.kt | 21 +--- .../tokendetails/ui/TokenDetailsScreen.kt | 17 +--- .../ui/components/TokenDetailsBalanceBlock.kt | 17 +--- .../ui/components/TokenDetailsTopAppBar.kt | 36 ++----- .../ui/components/TokenInfoBlock.kt | 18 +--- .../components/exchange/ExchangeProvider.kt | 3 +- .../exchange/ExchangeStatusItems.kt | 27 +----- .../common/component/NetworkGroupItem.kt | 15 +-- .../common/component/TokenItem.kt | 3 +- .../organizetokens/OrganizeTokensScreen.kt | 17 +--- .../ui/components/TokenActionsBottomSheet.kt | 17 +--- .../wallet/ui/components/WalletsList.kt | 19 +--- .../ui/components/common/WalletBottomSheet.kt | 5 +- .../wallet/ui/components/common/WalletCard.kt | 15 +-- .../ui/components/common/WalletTopBar.kt | 15 +-- .../components/visa/BalancesAndLimitsBlock.kt | 17 +--- .../visa/BalancesAndLimitsBottomSheet.kt | 17 +--- .../visa/VisaTxDetailsBottomSheet.kt | 17 +--- 131 files changed, 736 insertions(+), 1778 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt index 55afd696af..afff4bf8e5 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenFloatingButton import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenForm @@ -70,7 +71,7 @@ internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content, mod @Preview @Composable private fun Preview_AddCustomTokenContent() { - TangemTheme { + TangemThemePreview { AddCustomTokenContent(state = AddCustomTokenPreviewData.createContent()) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt index 3824462da3..d2e3729321 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt @@ -1,11 +1,12 @@ package com.tangem.tap.features.customtoken.impl.presentation.ui +import android.content.res.Configuration import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder /** @@ -24,21 +25,12 @@ internal fun AddCustomTokenScreen(stateHolder: AddCustomTokenStateHolder, modifi } @Preview(showSystemUi = true) +@Preview(showSystemUi = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_AddCustomTokenScreen_Light( +private fun Preview_AddCustomTokenScreen( @PreviewParameter(AddCustomTokenScreenProvider::class) stateHolder: AddCustomTokenStateHolder, ) { - TangemTheme(isDark = false) { - AddCustomTokenScreen(stateHolder) - } -} - -@Preview(showSystemUi = true) -@Composable -private fun Preview_AddCustomTokenScreen_Dark( - @PreviewParameter(AddCustomTokenScreenProvider::class) stateHolder: AddCustomTokenStateHolder, -) { - TangemTheme(isDark = true) { + TangemThemePreview { AddCustomTokenScreen(stateHolder) } } diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt index 48ffb09110..3bb17cddb9 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt @@ -19,6 +19,7 @@ import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SpacerH8 import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TestTokenItem import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock @@ -242,7 +243,7 @@ private fun TestBlock( @Preview @Composable private fun Preview_AddCustomTokenTestContent() { - TangemTheme { + TangemThemePreview { AddCustomTokenTestContent(state = AddCustomTokenPreviewData.createTestContent()) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt index a55ee2f29e..3cc1d4847a 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.components.PrimaryButtonIconStart import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton import com.tangem.wallet.R @@ -42,7 +43,7 @@ internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton, m private fun Preview_AddCustomTokenFloatingButton( @PreviewParameter(AddCustomTokenFloatingButtonProvider::class) model: AddCustomTokenFloatingButton, ) { - TangemTheme { + TangemThemePreview { AddCustomTokenFloatingButton(model) } } diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt index dd1293ba65..58da365d81 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.tap.common.compose.TangemTextFieldsDefault import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField @@ -195,7 +196,7 @@ private fun SelectorField(model: AddCustomTokenSelectorField) { @Preview @Composable private fun Preview_AddCustomTokenForm(@PreviewParameter(AddCustomTokenFormProvider::class) model: AddCustomTokenForm) { - TangemTheme { + TangemThemePreview { AddCustomTokenForm(model) } } diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenToolbar.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenToolbar.kt index 81d0f52912..030f130a1a 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenToolbar.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenToolbar.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.tap.features.details.ui.cardsettings.resolveReference @@ -54,7 +55,7 @@ internal fun AddCustomTokenToolbar(title: TextReference, onBackButtonClick: () - @Preview @Composable internal fun Preview_AddCustomTokenToolbar() { - TangemTheme { + TangemThemePreview { AddCustomTokenToolbar(title = TextReference.Res(R.string.add_custom_token_title), onBackButtonClick = {}) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarnings.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarnings.kt index a00fc2d4fb..a323a8a982 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarnings.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarnings.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.customtoken.impl.presentation.ui.components +import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -15,6 +16,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.sp import com.tangem.core.ui.res.TangemColorPalette +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenPreviewData @@ -71,17 +73,10 @@ private fun AddCustomTokenWarning(warning: AddCustomTokenWarning) { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_AddCustomTokenWarnings_Light() { - TangemTheme { - AddCustomTokenWarnings(warnings = AddCustomTokenPreviewData.createWarnings()) - } -} - -@Preview -@Composable -private fun Preview_AddCustomTokenWarnings_Dark() { - TangemTheme(isDark = true) { +private fun Preview_AddCustomTokenWarnings() { + TangemThemePreview { AddCustomTokenWarnings(warnings = AddCustomTokenPreviewData.createWarnings()) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorScreen.kt index a38cac5c0a..b29b503a39 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorScreen.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.ui.appcurrency +import android.content.res.Configuration import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -25,6 +26,7 @@ import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.SpacerW import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.event.consumedEvent +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorState.Currency import com.tangem.wallet.R @@ -302,21 +304,12 @@ private val RadioButtonColors: RadioButtonColors // region Preview @Preview(showBackground = true, widthDp = 360, heightDp = 720) +@Preview(showBackground = true, widthDp = 360, heightDp = 720, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun AppCurrencySelectorScreenPreview_Light( +private fun AppCurrencySelectorScreenPreview( @PreviewParameter(AppCurrencySelectorStateProvider::class) param: AppCurrencySelectorState, ) { - TangemTheme(isDark = false) { - AppCurrencySelectorScreen(param) - } -} - -@Preview(showBackground = true, widthDp = 360, heightDp = 720) -@Composable -private fun AppCurrencySelectorScreenPreview_Dark( - @PreviewParameter(AppCurrencySelectorStateProvider::class) param: AppCurrencySelectorState, -) { - TangemTheme(isDark = true) { + TangemThemePreview { AppCurrencySelectorScreen(param) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt index 7c99ed8b24..c60f34ecce 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.ui.appsettings +import android.content.res.Configuration import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -10,6 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item @@ -70,21 +72,12 @@ private fun AppSettings(state: AppSettingsScreenState.Content) { // region Preview @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun AppSettingsScreenPreview_Light( +private fun AppSettingsScreenPreview( @PreviewParameter(AppSettingsScreenStateProvider::class) state: AppSettingsScreenState, ) { - TangemTheme { - AppSettingsScreen(state = state, onBackClick = {}) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun AppSettingsScreenPreview_Dark( - @PreviewParameter(AppSettingsScreenStateProvider::class) state: AppSettingsScreenState, -) { - TangemTheme(isDark = true) { + TangemThemePreview { AppSettingsScreen(state = state, onBackClick = {}) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt index 1dce42e622..b7b5b15da2 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.ui.appsettings.components +import android.content.res.Configuration import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -8,7 +9,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButton import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.tap.features.details.ui.appsettings.AppSettingsDialogsFactory import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Dialog import com.tangem.wallet.R @@ -34,17 +35,10 @@ internal fun SettingsAlertDialog(dialog: Dialog.Alert) { // region Preview @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun AlertDialogPreview_Light(@PreviewParameter(AlertDialogProvider::class) dialog: Dialog.Alert) { - TangemTheme { - SettingsAlertDialog(dialog = dialog) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun AlertDialogPreview_Dark(@PreviewParameter(AlertDialogProvider::class) dialog: Dialog.Alert) { - TangemTheme(isDark = true) { +private fun AlertDialogPreview(@PreviewParameter(AlertDialogProvider::class) dialog: Dialog.Alert) { + TangemThemePreview { SettingsAlertDialog(dialog = dialog) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsButtonItem.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsButtonItem.kt index 946c62d2f2..17dccc7a63 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsButtonItem.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsButtonItem.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.ui.appsettings.components +import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -14,6 +15,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item @@ -52,17 +54,10 @@ internal fun SettingsButtonItem(item: Item.Button, modifier: Modifier = Modifier // region Preview @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun ButtonItemPreview_Light(@PreviewParameter(ButtonItemProvider::class) item: Item.Button) { - TangemTheme { - SettingsButtonItem(item = item) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun ButtonItemPreview_Dark(@PreviewParameter(ButtonItemProvider::class) item: Item.Button) { - TangemTheme(isDark = true) { +private fun ButtonItemPreview(@PreviewParameter(ButtonItemProvider::class) item: Item.Button) { + TangemThemePreview { SettingsButtonItem(item = item) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsCardItem.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsCardItem.kt index 1560b14a43..1e43c8f328 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsCardItem.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsCardItem.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.ui.appsettings.components +import android.content.res.Configuration import androidx.compose.foundation.layout.* import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon @@ -16,6 +17,7 @@ import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.SpacerH4 import com.tangem.core.ui.components.SpacerW16 import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item @@ -59,17 +61,10 @@ internal fun SettingsCardItem(item: Item.Card, modifier: Modifier = Modifier) { // region Preview @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun CardItemPreview_Light(@PreviewParameter(CardItemProvider::class) item: Item.Card) { - TangemTheme { - SettingsCardItem(item = item) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun CardItemPreview_Dark(@PreviewParameter(CardItemProvider::class) item: Item.Card) { - TangemTheme(isDark = true) { +private fun CardItemPreview(@PreviewParameter(CardItemProvider::class) item: Item.Card) { + TangemThemePreview { SettingsCardItem(item = item) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSelectorDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSelectorDialog.kt index 8c1a93e307..41b568b7ed 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSelectorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSelectorDialog.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.ui.appsettings.components +import android.content.res.Configuration import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -8,7 +9,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import com.tangem.core.ui.components.DialogButton import com.tangem.core.ui.components.SelectorDialog import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.tap.features.details.ui.appsettings.AppSettingsDialogsFactory import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Dialog import com.tangem.wallet.R @@ -31,17 +32,10 @@ internal fun SettingsSelectorDialog(dialog: Dialog.Selector) { // region Preview @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun SettingsSelectorDialogPreview_Light(@PreviewParameter(DialogProvider::class) param: Dialog.Selector) { - TangemTheme(isDark = false) { - SettingsSelectorDialog(param) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun SettingsSelectorDialogPreview_Dark(@PreviewParameter(DialogProvider::class) param: Dialog.Selector) { - TangemTheme(isDark = true) { +private fun SettingsSelectorDialogPreview(@PreviewParameter(DialogProvider::class) param: Dialog.Selector) { + TangemThemePreview { SettingsSelectorDialog(param) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt index dfbc486b5c..a8476e3bd2 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.ui.appsettings.components +import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -16,6 +17,7 @@ import com.tangem.core.ui.components.SpacerH4 import com.tangem.core.ui.components.SpacerW32 import com.tangem.core.ui.components.TangemSwitch import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item @@ -71,17 +73,10 @@ internal fun SettingsSwitchItem(item: Item.Switch, modifier: Modifier = Modifier // region Preview @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun SwitchItemPreview_Light(@PreviewParameter(SwitchItemProvider::class) item: Item.Switch) { - TangemTheme { - SettingsSwitchItem(item = item) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun SwitchItemPreview_Dark(@PreviewParameter(SwitchItemProvider::class) item: Item.Switch) { - TangemTheme(isDark = true) { +private fun SwitchItemPreview(@PreviewParameter(SwitchItemProvider::class) item: Item.Switch) { + TangemThemePreview { SettingsSwitchItem(item = item) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt index b528123bb7..b0e2785b32 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.ui.cardsettings +import android.content.res.Configuration import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -12,6 +13,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.ui.common.DetailsMainButton import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold @@ -182,17 +184,10 @@ private fun CardSettingsScreenStateSample() { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun CardSettingsScreenStatePreview_Light() { - TangemTheme { - CardSettingsScreenStateSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun CardSettingsScreenStatePreview_Dark() { - TangemTheme(isDark = true) { +private fun CardSettingsScreenStatePreview() { + TangemThemePreview { CardSettingsScreenStateSample() } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt index ec5a34ac4e..f31718287c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.ui.details +import android.content.res.Configuration import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow @@ -23,6 +24,7 @@ import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemColorPalette +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.ui.common.ScreenTitle import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold @@ -236,19 +238,10 @@ private fun TangemAppVersion(appNameRes: Int, version: String, modifier: Modifie // region Preview @Preview(showBackground = true, widthDp = 360, heightDp = 900) +@Preview(showBackground = true, widthDp = 360, heightDp = 900, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun DetailsScreenPreview_Light( - @PreviewParameter(DetailsScreenStateProvider::class) param: DetailsScreenState, -) { - TangemTheme(isDark = false) { - DetailsScreen(param, onBackClick = {}) - } -} - -@Preview(showBackground = true, widthDp = 360, heightDp = 900) -@Composable -private fun DetailsScreenPreview_Dark(@PreviewParameter(DetailsScreenStateProvider::class) param: DetailsScreenState) { - TangemTheme(isDark = true) { +private fun DetailsScreenPreview(@PreviewParameter(DetailsScreenStateProvider::class) param: DetailsScreenState) { + TangemThemePreview { DetailsScreen(param, onBackClick = {}) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index 35b4c9bae8..b0ccd93897 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.details.ui.resetcard +import android.content.res.Configuration import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.* import androidx.compose.foundation.layout.* @@ -12,6 +13,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.* +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.tap.features.details.ui.cardsettings.resolveReference @@ -228,17 +230,10 @@ private fun ResetCardScreenSample(modifier: Modifier = Modifier) { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun ResetCardScreenPreview_Light() { - TangemTheme { - ResetCardScreenSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun ResetCardScreenPreview_Dark() { - TangemTheme(isDark = true) { +private fun ResetCardScreenPreview() { + TangemThemePreview { ResetCardScreenSample() } } diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt index 844d5e6663..1b0c69fb9c 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.test.TestTags import com.tangem.tap.features.home.compose.content.* import com.tangem.tap.features.home.compose.views.HomeButtons @@ -213,7 +214,7 @@ private data class StoriesScreenContentConfig( private fun StoriesScreenContentPreview( @PreviewParameter(StoriesScreenContentConfigProvider::class) config: StoriesScreenContentConfig, ) { - TangemTheme { + TangemThemePreview { StoriesScreenContent(config = config) } } diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt index 3a9ebaf1bf..be6823d684 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import com.tangem.core.ui.components.SpacerW12 import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.test.TestTags import com.tangem.wallet.R @@ -73,7 +74,7 @@ private fun OrderCardButton(onClick: () -> Unit, modifier: Modifier = Modifier) @Preview(showBackground = true, widthDp = 360) @Composable private fun HomeButtonsPreview(@PreviewParameter(HomeButtonsParameterProvider::class) state: HomeButtonsState) { - TangemTheme { + TangemThemePreview { Box( modifier = Modifier.background(Color.Black), ) { diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/SearchCurrenciesButton.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/SearchCurrenciesButton.kt index 4e91ce98bc..e3e237954e 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/SearchCurrenciesButton.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/SearchCurrenciesButton.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.wallet.R @Composable @@ -29,7 +30,7 @@ internal fun SearchCurrenciesButton(onClick: () -> Unit, modifier: Modifier = Mo @Preview(showBackground = true, widthDp = 360) @Composable private fun SearchCurrenciesButtonPreview() { - TangemTheme { + TangemThemePreview { Box( modifier = Modifier .background(color = Color.Black) diff --git a/app/src/main/java/com/tangem/tap/features/main/ui/components/ModalNotificationScreen.kt b/app/src/main/java/com/tangem/tap/features/main/ui/components/ModalNotificationScreen.kt index 587b734dc0..72c36caf54 100644 --- a/app/src/main/java/com/tangem/tap/features/main/ui/components/ModalNotificationScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/main/ui/components/ModalNotificationScreen.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.main.ui.components +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.Icon @@ -17,6 +18,7 @@ import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.main.model.ActionConfig import com.tangem.tap.features.main.model.ModalNotification @@ -82,27 +84,12 @@ internal fun ModalNotificationContent(notification: ModalNotification, modifier: // region Preview @Preview(widthDp = 360, heightDp = 404) +@Preview(widthDp = 360, heightDp = 404, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun ModalNotificationContentPreview_Light( +private fun ModalNotificationContentPreview( @PreviewParameter(GlobalNotificationProvider::class) param: ModalNotification, ) { - TangemTheme(isDark = false) { - ModalNotificationContent( - notification = param, - modifier = Modifier.background( - color = TangemTheme.colors.background.primary, - shape = TangemTheme.shapes.bottomSheet, - ), - ) - } -} - -@Preview(widthDp = 360, heightDp = 404) -@Composable -private fun ModalNotificationContentPreview_Dark( - @PreviewParameter(GlobalNotificationProvider::class) param: ModalNotification, -) { - TangemTheme(isDark = true) { + TangemThemePreview { ModalNotificationContent( notification = param, modifier = Modifier.background( diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt index 9113d5001d..115bd8d273 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.saveWallet.ui.components +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable @@ -9,6 +10,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButton +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog @@ -40,17 +42,10 @@ private fun EnrollBiometricDialogContentSample(modifier: Modifier = Modifier) { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun EnrollBiometricDialogContentPreview_Light() { - TangemTheme { - EnrollBiometricDialogContentSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun EnrollBiometricDialogContentPreview_Dark() { - TangemTheme(isDark = true) { +private fun EnrollBiometricDialogContentPreview() { + TangemThemePreview { EnrollBiometricDialogContentSample() } } diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt index a8bbc46f02..c7679a466d 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.saveWallet.ui.components +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -27,6 +28,7 @@ import com.tangem.core.ui.components.SpacerHHalf import com.tangem.core.ui.components.SpacerW24 import com.tangem.core.ui.components.SpacerW8 import com.tangem.core.ui.components.atoms.Hand +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.wallet.R @@ -196,17 +198,10 @@ private fun SaveWalletScreenContentSample(modifier: Modifier = Modifier) { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun SaveWalletScreenContentPreview_Light() { - TangemTheme { - SaveWalletScreenContentSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun SaveWalletScreenContentPreview_Dark() { - TangemTheme(isDark = true) { +private fun SaveWalletScreenContentPreview() { + TangemThemePreview { SaveWalletScreenContentSample() } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/DetailedNetworksList.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/DetailedNetworksList.kt index 806c161a01..12620898b6 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/DetailedNetworksList.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/DetailedNetworksList.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.tokens.impl.presentation.ui +import android.content.res.Configuration import androidx.compose.animation.* import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable @@ -22,6 +23,7 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.sp import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.tap.features.tokens.impl.presentation.states.NetworkItemState import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState import kotlinx.collections.immutable.ImmutableCollection @@ -135,9 +137,10 @@ private fun RowScope.NetworkTitle(model: NetworkItemState) { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_DetailedNetworksList_ManageAccess_Light() { - TangemTheme { +private fun Preview_DetailedNetworksList_ManageAccess() { + TangemThemePreview { DetailedNetworksList( isExpanded = true, token = TokenListPreviewData.createManageToken(), @@ -147,33 +150,10 @@ private fun Preview_DetailedNetworksList_ManageAccess_Light() { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_DetailedNetworksList_ManageAccess_Dark() { - TangemTheme { - DetailedNetworksList( - isExpanded = true, - token = TokenListPreviewData.createManageToken(), - networks = TokenListPreviewData.createManageNetworksList(), - ) - } -} - -@Preview -@Composable -private fun Preview_DetailedNetworksList_ReadAccess_Light() { - TangemTheme { - DetailedNetworksList( - isExpanded = true, - token = TokenListPreviewData.createReadToken(), - networks = TokenListPreviewData.createReadNetworksList(), - ) - } -} - -@Preview -@Composable -private fun Preview_DetailedNetworksList_ReadAccess_Dark() { - TangemTheme { +private fun Preview_DetailedNetworksList_ReadAccess() { + TangemThemePreview { DetailedNetworksList( isExpanded = true, token = TokenListPreviewData.createReadToken(), diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/NetworkItemArrow.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/NetworkItemArrow.kt index 671961ab61..b51b65f1c0 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/NetworkItemArrow.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/NetworkItemArrow.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.tokens.impl.presentation.ui +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.Icon @@ -9,6 +10,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.wallet.R @@ -39,25 +41,10 @@ internal fun NetworkItemArrow(itemHeight: Dp, isLastItem: Boolean) { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_NetworkItemArrow_Column_Light() { - TangemTheme(isDark = false) { - Column( - modifier = Modifier - .fillMaxWidth() - .background(color = TangemTheme.colors.background.primary) - .padding(start = TangemTheme.dimens.size36), - ) { - NetworkItemArrow(itemHeight = TangemTheme.dimens.size62, isLastItem = false) - NetworkItemArrow(itemHeight = TangemTheme.dimens.size62, isLastItem = true) - } - } -} - -@Preview -@Composable -private fun Preview_NetworkItemArrow_Column_Dark() { - TangemTheme(isDark = true) { +private fun Preview_NetworkItemArrow_Column() { + TangemThemePreview { Column( modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt index bae044a2a6..ce1967b48b 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.tokens.impl.presentation.ui +import android.content.res.Configuration import androidx.compose.animation.* import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme @@ -24,6 +25,7 @@ import androidx.constraintlayout.compose.Dimension import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest import com.tangem.core.ui.components.CurrencyPlaceholderIcon +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.ImageBackgroundContrastChecker import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState @@ -216,33 +218,19 @@ private fun ChangeNetworksViewButton(isExpanded: Boolean, onClick: () -> Unit, m } @Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_TokenItem_ManageAccess_Light() { - TangemTheme(isDark = false) { - TokenItem(model = TokenListPreviewData.createManageToken()) - } -} - -@Preview() -@Composable -private fun Preview_TokenItem_ManageAccess_Dark() { - TangemTheme(isDark = true) { +private fun Preview_TokenItem_ManageAccess() { + TangemThemePreview { TokenItem(model = TokenListPreviewData.createManageToken()) } } @Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_TokenItem_ReadAccess_Light() { - TangemTheme(isDark = false) { - TokenItem(model = TokenListPreviewData.createReadToken()) - } -} - -@Preview() -@Composable -private fun Preview_TokenItem_ReadAccess_Dark() { - TangemTheme(isDark = true) { +private fun Preview_TokenItem_ReadAccess() { + TangemThemePreview { TokenItem(model = TokenListPreviewData.createReadToken()) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt index 70335b9609..56366dfb51 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.tokens.impl.presentation.ui +import android.content.res.Configuration import androidx.activity.compose.BackHandler import androidx.compose.animation.Crossfade import androidx.compose.foundation.background @@ -34,6 +35,7 @@ import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.itemContentType import androidx.paging.compose.itemKey import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState import com.tangem.tap.features.tokens.impl.presentation.states.TokensListStateHolder @@ -193,21 +195,12 @@ private fun SaveChangesButton(showProgress: Boolean, onClick: () -> Unit, modifi } @Preview(showSystemUi = true) +@Preview(showSystemUi = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_TokensListScreen_Light( +private fun Preview_TokensListScreen( @PreviewParameter(TokensListScreenProvider::class) stateHolder: TokensListStateHolder, ) { - TangemTheme(isDark = false) { - TokensListScreen(stateHolder) - } -} - -@Preview(showSystemUi = true) -@Composable -private fun Preview_TokensListScreen_Dark( - @PreviewParameter(TokensListScreenProvider::class) stateHolder: TokensListStateHolder, -) { - TangemTheme(isDark = true) { + TangemThemePreview { TokensListScreen(stateHolder) } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListToolbar.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListToolbar.kt index 71aba31a33..5e0aafc999 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListToolbar.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListToolbar.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.sp import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.tap.features.tokens.impl.presentation.states.TokensListToolbarState import com.tangem.tap.features.tokens.impl.presentation.states.TokensListToolbarState.InputField import com.tangem.tap.features.tokens.impl.presentation.states.TokensListToolbarState.Title @@ -171,7 +172,7 @@ private fun Hint(value: String) { @Preview @Composable private fun Preview_AddTokensToolbar_EditAccess() { - TangemTheme { + TangemThemePreview { TokensListToolbar( state = Title.Manage( titleResId = R.string.main_manage_tokens, @@ -186,7 +187,7 @@ private fun Preview_AddTokensToolbar_EditAccess() { @Preview @Composable private fun Preview_AddTokensToolbar_ReadAccess() { - TangemTheme { + TangemThemePreview { TokensListToolbar( state = Title.Read( titleResId = R.string.common_search_tokens, @@ -202,7 +203,7 @@ private fun Preview_AddTokensToolbar_ReadAccess() { private fun Preview_AddTokensToolbar_SearchInputField() { var value by remember { mutableStateOf("") } - TangemTheme { + TangemThemePreview { TokensListToolbar( state = InputField( onBackButtonClick = {}, diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt index ca9a8d5769..a6315d5b1e 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.welcome.ui.components +import android.content.res.Configuration import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -7,7 +8,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButton -import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.tap.features.welcome.ui.model.WarningModel import com.tangem.wallet.R @@ -72,17 +73,10 @@ private fun BiometricsLockoutDialogSample(modifier: Modifier = Modifier) { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun BiometricsLockoutDialogPreview_Light() { - TangemTheme { - BiometricsLockoutDialogSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun BiometricsLockoutDialogPreview_Dark() { - TangemTheme(isDark = true) { +private fun BiometricsLockoutDialogPreview() { + TangemThemePreview { BiometricsLockoutDialogSample() } } @@ -100,17 +94,10 @@ private fun BiometricsLockoutDialog_Permanent_Sample(modifier: Modifier = Modifi } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun BiometricsLockoutDialog_Permanent_Preview_Light() { - TangemTheme { - BiometricsLockoutDialog_Permanent_Sample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun BiometricsLockoutDialog_Permanent_Preview_Dark() { - TangemTheme(isDark = true) { +private fun BiometricsLockoutDialog_Permanent_Preview() { + TangemThemePreview { BiometricsLockoutDialog_Permanent_Sample() } } @@ -123,17 +110,10 @@ private fun KeyInvalidatedWarningSample(modifier: Modifier = Modifier) { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun KeyInvalidatedWarningPreview_Light() { - TangemTheme { - KeyInvalidatedWarningSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun KeyInvalidatedWarningPreview_Dark() { - TangemTheme(isDark = true) { +private fun KeyInvalidatedWarningPreview() { + TangemThemePreview { KeyInvalidatedWarningSample() } } @@ -146,17 +126,10 @@ private fun BiometricDisabledWarningSample(modifier: Modifier = Modifier) { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun BiometricDisabledWarningPreview_Light() { - TangemTheme { - BiometricDisabledWarningSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun BiometricDisabledWarningPreview_Dark() { - TangemTheme(isDark = true) { +private fun BiometricDisabledWarningPreview() { + TangemThemePreview { BiometricDisabledWarningSample() } } diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt index cc83e0d893..5aca214e93 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.welcome.ui.components +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.Icon @@ -12,6 +13,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.* +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.wallet.R @@ -97,17 +99,10 @@ private fun WelcomeScreenContentSample(modifier: Modifier = Modifier) { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun WelcomeScreenContentPreview_Light() { - TangemTheme { - WelcomeScreenContentSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun WelcomeScreenContentPreview_Dark() { - TangemTheme(isDark = true) { +private fun WelcomeScreenContentPreview() { + TangemThemePreview { WelcomeScreenContentSample() } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index 1e2301844b..bb17fcf7b6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components +import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -12,6 +13,7 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.components.buttons.common.* +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme // region TextButton @@ -319,17 +321,10 @@ private fun PrimaryButtonSample() { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun PrimaryButtonPreview_Light() { - TangemTheme { - PrimaryButtonSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun PrimaryButtonPreview_Dark() { - TangemTheme(isDark = true) { +private fun PrimaryButtonPreview() { + TangemThemePreview { PrimaryButtonSample() } } @@ -383,17 +378,10 @@ private fun SecondaryButtonSample() { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun SecondaryButtonPreview_Light() { - TangemTheme { - SecondaryButtonSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun SecondaryButtonPreview_Dark() { - TangemTheme(isDark = true) { +private fun SecondaryButtonPreview() { + TangemThemePreview { SecondaryButtonSample() } } @@ -414,19 +402,11 @@ private fun TextButtonSample() { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun TextButtonPreview_LightTheme() { - TangemTheme { +private fun TextButtonPreview() { + TangemThemePreview { TextButtonSample() } } - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun TextButtonPreview_DarkTheme() { - TangemTheme(isDark = true) { - TextButtonSample() - } -} - // endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt b/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt index 5654a037e7..e8612296a5 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components +import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth @@ -16,15 +17,17 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.res.ButtonColorType +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TextColorType import com.tangem.core.ui.res.buttonColor import com.tangem.core.ui.res.textColor @Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Preview(widthDp = 328, heightDp = 48, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() { - TangemTheme(isDark = false) { +private fun Preview_PrimaryStartIconButton_Enabled() { + TangemThemePreview { PrimaryStartIconButton( text = "Manage tokens", iconResId = R.drawable.ic_tangem_24, @@ -35,22 +38,10 @@ private fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() { } @Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Preview(widthDp = 328, heightDp = 48, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_PrimaryStartIconButton_Enabled_InDarkTheme() { - TangemTheme(isDark = true) { - PrimaryStartIconButton( - text = "Manage tokens", - iconResId = R.drawable.ic_tangem_24, - enabled = true, - onClick = {}, - ) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -private fun Preview_PrimaryStartIconButton_Disabled_InLightTheme() { - TangemTheme(isDark = false) { +private fun Preview_PrimaryStartIconButton_Disabled() { + TangemThemePreview { PrimaryStartIconButton( text = "Manage tokens", iconResId = R.drawable.ic_tangem_24, @@ -61,22 +52,10 @@ private fun Preview_PrimaryStartIconButton_Disabled_InLightTheme() { } @Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Preview(widthDp = 328, heightDp = 48, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_PrimaryStartIconButton_Disabled_InDarkTheme() { - TangemTheme(isDark = true) { - PrimaryStartIconButton( - text = "Manage tokens", - iconResId = R.drawable.ic_tangem_24, - enabled = false, - onClick = {}, - ) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -private fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() { - TangemTheme(isDark = false) { +private fun Preview_PrimaryEndIconButton_Enabled() { + TangemThemePreview { PrimaryEndIconButton( text = "Manage tokens", iconResId = R.drawable.ic_tangem_24, @@ -87,35 +66,10 @@ private fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() { } @Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Preview(widthDp = 328, heightDp = 48, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_PrimaryEndIconButton_Enabled_InDarkTheme() { - TangemTheme(isDark = true) { - PrimaryEndIconButton( - text = "Manage tokens", - iconResId = R.drawable.ic_tangem_24, - enabled = true, - onClick = {}, - ) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -private fun Preview_PrimaryEndIconButton_Disabled_InLightTheme() { - TangemTheme(isDark = false) { - PrimaryEndIconButton( - text = "Manage tokens", - iconResId = R.drawable.ic_tangem_24, - enabled = false, - onClick = {}, - ) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -private fun Preview_PrimaryEndIconButton_Disabled_InDarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_PrimaryEndIconButton_Disabled() { + TangemThemePreview { PrimaryEndIconButton( text = "Manage tokens", iconResId = R.drawable.ic_tangem_24, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt index 91dd9a2506..380c6bd97b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components +import android.content.res.Configuration import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -22,6 +23,7 @@ import com.tangem.core.ui.components.states.Item import com.tangem.core.ui.components.states.SelectableItemsState import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.valentinilk.shimmer.shimmer import kotlinx.collections.immutable.toImmutableList @@ -650,34 +652,19 @@ private fun CardsPreview() { } @Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_Cards_InLightTheme() { - TangemTheme(isDark = false) { - CardsPreview() - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview_InfoCardWithWarning_InDarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_Cards() { + TangemThemePreview { CardsPreview() } } @Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Preview(widthDp = 328, heightDp = 48, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_SimpleInfoCard_InLightTheme() { - TangemTheme(isDark = false) { - SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC") - SmallInfoCardWithDisclaimer(startText = "Balance", endText = "0.4405434 BTC", disclaimer = "test") - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -private fun Preview_SimpleInfoCard_InDarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_SimpleInfoCard() { + TangemThemePreview { SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC") SmallInfoCardWithDisclaimer(startText = "Balance", endText = "0.4405434 BTC", disclaimer = "test") } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/CurrencyPlaceholderIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/CurrencyPlaceholderIcon.kt index 8d10e7486c..71b0550226 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/CurrencyPlaceholderIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/CurrencyPlaceholderIcon.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding @@ -11,6 +12,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -39,19 +41,11 @@ fun CurrencyPlaceholderIcon(id: String, modifier: Modifier = Modifier) { // region preview @Preview(showBackground = true, heightDp = 40, widthDp = 40) +@Preview(showBackground = true, heightDp = 40, widthDp = 40, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_CurrencyPlaceholderIcon_InLightTheme() { - TangemTheme(isDark = false) { +private fun Preview_CurrencyPlaceholderIcon() { + TangemThemePreview { CurrencyPlaceholderIcon(id = "DAI") } } - -@Preview(showBackground = true, heightDp = 40, widthDp = 40) -@Composable -private fun Preview_CurrencyPlaceholderIcon_InDarkTheme() { - TangemTheme(isDark = true) { - CurrencyPlaceholderIcon(id = "DAI") - } -} - // endregion preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt index 658d6af9f6..18ca78091a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components +import android.content.res.Configuration import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -26,6 +27,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.SelctorDialogParamsProvider.SelectorDialogParams import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults import com.tangem.core.ui.components.fields.SimpleDialogTextField +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -450,33 +452,19 @@ private fun BasicDialogPreview() { } @Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_SimpleOkDialog_InLightTheme() { - TangemTheme(isDark = false) { +private fun Preview_SimpleOkDialog() { + TangemThemePreview { SimpleOkDialogPreview() } } @Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_BasicDialog_InLightTheme() { - TangemTheme(isDark = false) { - BasicDialogPreview() - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview_SimpleOkDialog_InDarkTheme() { - TangemTheme(isDark = true) { - SimpleOkDialogPreview() - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview_BasicDialog_InDarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_BasicDialog() { + TangemThemePreview { BasicDialogPreview() } } @@ -498,17 +486,10 @@ private fun WarningBasicDialogSample(modifier: Modifier = Modifier) { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun WarningBasicDialogPreview_Light() { - TangemTheme { - WarningBasicDialogSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun WarningBasicDialogPreview_Dark() { - TangemTheme(isDark = true) { +private fun WarningBasicDialogPreview() { + TangemThemePreview { WarningBasicDialogSample() } } @@ -532,44 +513,19 @@ private fun TextInputDialogSample(modifier: Modifier = Modifier) { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun TextInputDialogPreview_Light() { - TangemTheme { +private fun TextInputDialogPreview() { + TangemThemePreview { TextInputDialogSample() } } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun TextInputDialogPreview_Dark() { - TangemTheme(isDark = true) { - TextInputDialogSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun SelectorDialogPreview_Light( - @PreviewParameter(SelctorDialogParamsProvider::class) param: SelectorDialogParams, -) { - TangemTheme(isDark = false) { - SelectorDialog( - title = param.title, - items = param.items, - selectedItemIndex = param.selectedItemIndex, - confirmButton = DialogButton(title = "Cancel", onClick = {}), - onSelect = {}, - onDismissDialog = {}, - ) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun SelectorDialogPreview_Dark( - @PreviewParameter(SelctorDialogParamsProvider::class) param: SelectorDialogParams, -) { - TangemTheme(isDark = true) { +private fun SelectorDialogPreview(@PreviewParameter(SelctorDialogParamsProvider::class) param: SelectorDialogParams) { + TangemThemePreview { SelectorDialog( title = param.title, items = param.items, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Notifier.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Notifier.kt index a646f37f33..5e3e78a06a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Notifier.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Notifier.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.heightIn @@ -13,6 +14,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview /** * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=68%3A20&mode=design&t=WSV3AxC6zV1y0CHF-1) @@ -44,17 +46,10 @@ fun Notifier( } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun TangemNotifierPreview_Light(@PreviewParameter(NotifierProvider::class) text: String) { - TangemTheme(isDark = false) { - Notifier(text = text) - } -} - -@Preview -@Composable -private fun TangemNotifierPreview_Dark(@PreviewParameter(NotifierProvider::class) text: String) { - TangemTheme(isDark = true) { +private fun TangemNotifierPreview(@PreviewParameter(NotifierProvider::class) text: String) { + TangemThemePreview { Notifier(text = text) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt index 311fe1aae8..ddb5122ee1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components +import android.content.res.Configuration import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.infiniteRepeatable @@ -18,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import com.tangem.core.ui.res.LocalIsInDarkTheme import com.tangem.core.ui.res.TangemColorPalette +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.valentinilk.shimmer.* @@ -90,37 +92,24 @@ private val TangemShimmerColors: List // region preview +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun ShimmersPreview() { - Column( - modifier = Modifier - .fillMaxWidth() - .background(TangemTheme.colors.background.primary), - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18), - ) { - RectangleShimmer( + TangemThemePreview { + Column( modifier = Modifier .fillMaxWidth() - .height(TangemTheme.dimens.size24), - ) - CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42)) + .background(TangemTheme.colors.background.primary), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18), + ) { + RectangleShimmer( + modifier = Modifier + .fillMaxWidth() + .height(TangemTheme.dimens.size24), + ) + CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42)) + } } } - -@Preview(showBackground = true) -@Composable -private fun Shimmers_InLightTheme() { - TangemTheme(isDark = false) { - ShimmersPreview() - } -} - -@Preview(showBackground = true) -@Composable -private fun Shimmers_InDarkTheme() { - TangemTheme(isDark = true) { - ShimmersPreview() - } -} - // endregion preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt index f8312aa659..055eaab6f7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components +import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState @@ -25,6 +26,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -505,17 +507,10 @@ private fun OutlineTextFieldSample(modifier: Modifier = Modifier) { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun OutlineTextFieldPreview_Light() { - TangemTheme { - OutlineTextFieldSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun OutlineTextFieldPreview_Dark() { - TangemTheme(isDark = true) { +private fun OutlineTextFieldPreview() { + TangemThemePreview { OutlineTextFieldSample() } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt index fe2a681d08..cee6370513 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components +import android.content.res.Configuration import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -13,6 +14,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -179,42 +181,29 @@ private fun WarningCardMaterial3Style(onClick: (() -> Unit)? = null, content: @C // region Preview +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun WarningsPreview() { - Column(modifier = Modifier.fillMaxWidth()) { - WarningCard( - title = "Exchange rate has expired", - description = "To access all the networks, you need to scan the card.", - ) - SpacerH32() - ClickableWarningCard( - title = "Exchange rate has expired", - description = "To access all the networks, you need to scan the card.", - onClick = {}, - ) - SpacerH32() - RefreshableWarningCard( - title = "Exchange rate has expired", - description = "To access all the networks, you need to scan the card.", - onClick = {}, - ) + TangemThemePreview { + Column(modifier = Modifier.fillMaxWidth()) { + WarningCard( + title = "Exchange rate has expired", + description = "To access all the networks, you need to scan the card.", + ) + SpacerH32() + ClickableWarningCard( + title = "Exchange rate has expired", + description = "To access all the networks, you need to scan the card.", + onClick = {}, + ) + SpacerH32() + RefreshableWarningCard( + title = "Exchange rate has expired", + description = "To access all the networks, you need to scan the card.", + onClick = {}, + ) + } } } - -@Preview(showBackground = true) -@Composable -private fun Preview_Warning_InLightTheme() { - TangemTheme(isDark = false) { - WarningsPreview() - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview_Warning_InDarkTheme() { - TangemTheme(isDark = true) { - WarningsPreview() - } -} - // endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt index 1b910ca269..685358db13 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.appbar +import android.content.res.Configuration import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn @@ -15,6 +16,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.components.appbar.models.AdditionalButton +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -71,9 +73,10 @@ fun AppBarWithAdditionalButtons( } @Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Preview(widthDp = 360, heightDp = 56, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_AppBarWithAdditionalButtons_InLightTheme() { - TangemTheme(isDark = false) { +private fun Preview_AppBarWithAdditionalButtons() { + TangemThemePreview { AppBarWithAdditionalButtons( text = "Tangem", startButton = AdditionalButton( @@ -89,27 +92,10 @@ private fun Preview_AppBarWithAdditionalButtons_InLightTheme() { } @Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Preview(widthDp = 360, heightDp = 56, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_AppBarWithAdditionalButtons_InDarkTheme() { - TangemTheme(isDark = true) { - AppBarWithAdditionalButtons( - text = "Tangem", - startButton = AdditionalButton( - iconRes = R.drawable.ic_scan_24, - onIconClicked = {}, - ), - endButton = AdditionalButton( - iconRes = R.drawable.ic_more_vertical_24, - onIconClicked = {}, - ), - ) - } -} - -@Preview(widthDp = 360, heightDp = 56, showBackground = true) -@Composable -private fun Preview_AppBarWithOnlyStartButtons_InLightTheme() { - TangemTheme(isDark = false) { +private fun Preview_AppBarWithOnlyStartButtons() { + TangemThemePreview { AppBarWithAdditionalButtons( text = "Tangem", startButton = AdditionalButton( @@ -121,37 +107,10 @@ private fun Preview_AppBarWithOnlyStartButtons_InLightTheme() { } @Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Preview(widthDp = 360, heightDp = 56, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_AppBarWithOnlyStartButtons_InDarkTheme() { - TangemTheme(isDark = true) { - AppBarWithAdditionalButtons( - text = "Tangem", - startButton = AdditionalButton( - iconRes = R.drawable.ic_scan_24, - onIconClicked = {}, - ), - ) - } -} - -@Preview(widthDp = 360, heightDp = 56, showBackground = true) -@Composable -private fun Preview_AppBarWithOnlyEndButtons_InLightTheme() { - TangemTheme(isDark = false) { - AppBarWithAdditionalButtons( - text = "Tangem", - endButton = AdditionalButton( - iconRes = R.drawable.ic_more_vertical_24, - onIconClicked = {}, - ), - ) - } -} - -@Preview(widthDp = 360, heightDp = 56, showBackground = true) -@Composable -private fun Preview_AppBarWithOnlyEndButtons_InDarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_AppBarWithOnlyEndButtons() { + TangemThemePreview { AppBarWithAdditionalButtons( text = "Tangem", endButton = AdditionalButton( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt index afb57565f8..80d3014615 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.appbar +import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -15,6 +16,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -67,17 +69,10 @@ fun AppBarWithBackButton( } @Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Preview(widthDp = 360, heightDp = 56, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun PreviewAppBarWithBackButtonInLightTheme() { - TangemTheme(isDark = false) { - AppBarWithBackButton(text = "Title", onBackClick = {}) - } -} - -@Preview(widthDp = 360, heightDp = 56, showBackground = true) -@Composable -private fun PreviewAppBarWithBackButtonInDarkTheme() { - TangemTheme(isDark = true) { +private fun PreviewAppBarWithBackButton() { + TangemThemePreview { AppBarWithBackButton(text = "Title", onBackClick = {}) } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIcon.kt index 405217433f..0006bdad9d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIcon.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.appbar +import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.animation.* import androidx.compose.foundation.clickable @@ -11,6 +12,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme @Composable @@ -53,22 +55,10 @@ fun AppBarWithBackButtonAndIcon( } @Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Preview(widthDp = 360, heightDp = 56, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun PreviewAppBarWithBackButtonAndIconInLightTheme() { - TangemTheme(isDark = false) { - AppBarWithBackButtonAndIcon( - text = "Title", - iconRes = R.drawable.ic_qrcode_scan_24, - onBackClick = {}, - onIconClick = {}, - ) - } -} - -@Preview(widthDp = 360, heightDp = 56, showBackground = true) -@Composable -private fun PreviewAppBarWithBackButtonAndIconInDarkTheme() { - TangemTheme(isDark = true) { +private fun PreviewAppBarWithBackButtonAndIcon() { + TangemThemePreview { AppBarWithBackButtonAndIcon( text = "Title", iconRes = R.drawable.ic_qrcode_scan_24, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt index c65bf02622..fae3598a38 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.appbar +import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.animation.* import androidx.compose.foundation.background @@ -17,6 +18,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -98,34 +100,10 @@ fun AppBarWithBackButtonAndIconContent( } @Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Preview(widthDp = 360, heightDp = 56, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun PreviewAppBarWithBackButtonAndIconInLightTheme() { - TangemTheme(isDark = false) { - AppBarWithBackButtonAndIconContent( - text = "Title", - onBackClick = {}, - iconContent = { - Row { - Icon( - painter = painterResource(id = R.drawable.ic_qrcode_scan_24), - tint = TangemTheme.colors.icon.primary1, - contentDescription = null, - ) - Icon( - painter = painterResource(id = R.drawable.ic_flash_on_24), - tint = TangemTheme.colors.icon.primary1, - contentDescription = null, - ) - } - }, - ) - } -} - -@Preview(widthDp = 360, heightDp = 56, showBackground = true) -@Composable -private fun PreviewAppBarWithBackButtonAndIconInDarkTheme() { - TangemTheme(isDark = true) { +private fun PreviewAppBarWithBackButtonAndIcon() { + TangemThemePreview { AppBarWithBackButtonAndIconContent( text = "Title", subtitle = "Subtitle", diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt index 4dfd01d36d..ab6996b21d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt @@ -26,6 +26,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.TangemTextFieldsDefault import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview /** * App bar with close icon and search functionality @@ -228,7 +229,7 @@ private fun ExpandedSearchView( @Preview @Composable private fun CollapsedSearchViewPreview() { - TangemTheme { + TangemThemePreview { ExpandableSearchView( title = "Choose Token", onBackClick = {}, @@ -245,7 +246,7 @@ private fun CollapsedSearchViewPreview() { @Preview @Composable private fun ExpandedSearchViewPreview() { - TangemTheme { + TangemThemePreview { ExpandableSearchView( title = "Choose Token", onBackClick = {}, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt index f3d155f5a2..1f9dd3beca 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.atoms +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable @@ -8,6 +9,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -52,17 +54,10 @@ private fun HandSample(modifier: Modifier = Modifier) { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun HandPreview_Light() { - TangemTheme { - HandSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun HandPreview_Dark() { - TangemTheme(isDark = true) { +private fun HandPreview() { + TangemThemePreview { HandSample() } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/EllipsisText.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/EllipsisText.kt index d95b813ee1..d7ed2b5dd2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/EllipsisText.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/EllipsisText.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.TextUnit import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview sealed class TextEllipsis { @@ -219,7 +220,7 @@ private fun offsetEndEllipsisText( @Preview(widthDp = 200) @Composable private fun EllipsisTexPreview(@PreviewParameter(EllipsisTexPreviewParameterProvider::class) ellipsis: TextEllipsis) { - TangemTheme { + TangemThemePreview { EllipsisText( text = "11111111111111111111111111111111111111111111111111 END", ellipsis = ellipsis, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt index a6c3ee8402..715a630ed3 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.buttons +import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth @@ -15,6 +16,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.buttons.actions.ActionButton import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -41,21 +43,12 @@ fun HorizontalActionChips( // region Preview @Preview(widthDp = 360) +@Preview(widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_HorizontalActionChips_Light( +private fun Preview_HorizontalActionChips( @PreviewParameter(ActionButtonConfigProvider::class) buttons: HorizontalActionChips, ) { - TangemTheme(isDark = false) { - HorizontalActionChips(buttons = buttons.buttons) - } -} - -@Preview(widthDp = 360) -@Composable -private fun Preview_HorizontalActionChips_Dark( - @PreviewParameter(ActionButtonConfigProvider::class) buttons: HorizontalActionChips, -) { - TangemTheme(isDark = true) { + TangemThemePreview { HorizontalActionChips(buttons = buttons.buttons) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt index 60bc4a690f..15f640bdbf 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.buttons +import android.content.res.Configuration import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -14,6 +15,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -90,17 +92,10 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier: } @Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_SmallButton_Light() { - TangemTheme(isDark = false) { - ButtonsSample() - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview_SmallButton_Dark() { - TangemTheme(isDark = true) { +private fun Preview_SmallButton() { + TangemThemePreview { ButtonsSample() } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt index 3d0b87e26a..6c7e926dae 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.buttons.actions +import android.content.res.Configuration import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -23,6 +24,7 @@ import com.tangem.core.ui.components.SpacerW8 import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview /** * Rounded action button @@ -133,33 +135,19 @@ private fun Button( } @Preview(group = "RoundedActionButton", showBackground = true) +@Preview(group = "RoundedActionButton", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_RoundedActionButton_Light(@PreviewParameter(ActionStateProvider::class) state: ActionButtonConfig) { - TangemTheme(isDark = false) { - RoundedActionButton(state) - } -} - -@Preview(group = "RoundedActionButton", showBackground = true) -@Composable -private fun Preview_RoundedActionButton_Dark(@PreviewParameter(ActionStateProvider::class) state: ActionButtonConfig) { - TangemTheme(isDark = true) { +private fun Preview_RoundedActionButton(@PreviewParameter(ActionStateProvider::class) state: ActionButtonConfig) { + TangemThemePreview { RoundedActionButton(state) } } @Preview(group = "ActionButton", showBackground = true) +@Preview(group = "ActionButton", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_ActionButton_Light(@PreviewParameter(ActionStateProvider::class) state: ActionButtonConfig) { - TangemTheme(isDark = false) { - ActionButton(state) - } -} - -@Preview(group = "ActionButton", showBackground = true) -@Composable -private fun Preview_ActionButton_Dark(@PreviewParameter(ActionStateProvider::class) state: ActionButtonConfig) { - TangemTheme(isDark = true) { +private fun Preview_ActionButton(@PreviewParameter(ActionStateProvider::class) state: ActionButtonConfig) { + TangemThemePreview { ActionButton(state) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt index f3cbfca76d..0bfeb822a6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.buttons.segmentedbutton +import android.content.res.Configuration import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring @@ -20,6 +21,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf @@ -102,29 +104,12 @@ inline fun SegmentedButtons( } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun SegmentedButtonsPreview_Light( +private fun SegmentedButtonsPreview( @PreviewParameter(SegmentedButtonsPreviewProvider::class) config: PersistentList, ) { - TangemTheme { - SegmentedButtons( - config = config, - onClick = {}, - ) { - Text( - text = it.text, - modifier = Modifier.padding(TangemTheme.dimens.spacing16), - ) - } - } -} - -@Preview -@Composable -private fun SegmentedButtonsPreview_Dark( - @PreviewParameter(SegmentedButtonsPreviewProvider::class) config: PersistentList, -) { - TangemTheme(isDark = true) { + TangemThemePreview { SegmentedButtons( config = config, onClick = {}, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt index c095c2b8fd..73a58d56a1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.* import java.text.DecimalFormat @@ -128,7 +129,7 @@ private fun AmountTextFieldPreview( @PreviewParameter(AmountTextFieldPreviewProvider::class) amount: AmountTextFieldPreviewData, ) { var text by remember { mutableStateOf(amount.value.orEmpty()) } - TangemTheme { + TangemThemePreview { AmountTextField( value = text, decimals = amount.decimals, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/icons/identicon/IdentIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/icons/identicon/IdentIcon.kt index 23f26292ee..c4f107369a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/icons/identicon/IdentIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/icons/identicon/IdentIcon.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.icons.identicon +import android.content.res.Configuration import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size @@ -13,6 +14,7 @@ import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.toLowerCase import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.res.LocalIsInDarkTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme private const val GAP_WIDTH = 1f @@ -62,9 +64,10 @@ fun IdentIcon(address: String, modifier: Modifier = Modifier) { //region preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun IdentIconPreview_Light() { - TangemTheme { +private fun IdentIconPreview() { + TangemThemePreview { IdentIcon( address = "0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359", modifier = Modifier @@ -72,17 +75,4 @@ private fun IdentIconPreview_Light() { ) } } - -@Preview -@Composable -private fun IdentIconPreview_Dark() { - TangemTheme(isDark = true) { - IdentIcon( - address = "0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359", - modifier = Modifier - .size(TangemTheme.dimens.size40), - ) - } -} - //endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowApprox.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowApprox.kt index 6310a11f5a..2bd8311866 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowApprox.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowApprox.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.inputrow +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.Icon @@ -16,6 +17,7 @@ import com.tangem.core.ui.components.currency.tokenicon.TokenIconState import com.tangem.core.ui.components.inputrow.inner.DividerContainer import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -123,40 +125,36 @@ private fun InputRowApproxItem( //region Preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun InputRowApproxPreview_Light() { - TangemTheme { - InputRowApprox( - leftIcon = TokenIconState.Loading, - leftTitle = TextReference.Str("Left title USD"), - leftSubtitle = TextReference.Str("Left subtitle USD"), - leftTitleEllipsisOffset = 3, - rightIcon = TokenIconState.Loading, - rightTitle = TextReference.Str("Right title Right title Right title Right title Right title USD"), - rightSubtitle = TextReference.Str("Right subtitle Right subtitle Right subtitle USD"), - rightTitleEllipsisOffset = 3, - modifier = Modifier - .background(TangemTheme.colors.background.action), - ) - } -} - -@Preview -@Composable -private fun InputRowApproxPreview_Dark() { - TangemTheme(isDark = true) { - InputRowApprox( - leftIcon = TokenIconState.Loading, - leftTitle = TextReference.Str("Left title Left title Left title Left title Left title USD"), - leftSubtitle = TextReference.Str("Left subtitle Left subtitle Left subtitle USD"), - leftTitleEllipsisOffset = 3, - rightIcon = TokenIconState.Loading, - rightTitle = TextReference.Str("Right title USD"), - rightSubtitle = TextReference.Str("Right subtitle USD"), - rightTitleEllipsisOffset = 3, - modifier = Modifier - .background(TangemTheme.colors.background.action), - ) +private fun InputRowApproxPreview() { + TangemThemePreview { + Column { + InputRowApprox( + leftIcon = TokenIconState.Loading, + leftTitle = TextReference.Str("Left title USD"), + leftSubtitle = TextReference.Str("Left subtitle USD"), + leftTitleEllipsisOffset = 3, + rightIcon = TokenIconState.Loading, + rightTitle = TextReference.Str("Right title Right title Right title Right title Right title USD"), + rightSubtitle = TextReference.Str("Right subtitle Right subtitle Right subtitle USD"), + rightTitleEllipsisOffset = 3, + modifier = Modifier + .background(TangemTheme.colors.background.action), + ) + InputRowApprox( + leftIcon = TokenIconState.Loading, + leftTitle = TextReference.Str("Left title Left title Left title Left title Left title USD"), + leftSubtitle = TextReference.Str("Left subtitle Left subtitle Left subtitle USD"), + leftTitleEllipsisOffset = 3, + rightIcon = TokenIconState.Loading, + rightTitle = TextReference.Str("Right title USD"), + rightSubtitle = TextReference.Str("Right subtitle USD"), + rightTitleEllipsisOffset = 3, + modifier = Modifier + .background(TangemTheme.colors.background.action), + ) + } } } //endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowBestRate.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowBestRate.kt index 620e3346cf..3f711de362 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowBestRate.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowBestRate.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.inputrow +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -27,6 +28,7 @@ import com.tangem.core.ui.components.currency.tokenicon.LoadingIcon import com.tangem.core.ui.components.inputrow.inner.DividerContainer import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -154,30 +156,12 @@ private fun InnerIcon(imageUrl: String) { //region preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun InputRowBestRatePreview_Light( +private fun InputRowBestRatePreview( @PreviewParameter(InputRowBestRatePreviewDataProvider::class) data: InputRowBestRatePreviewData, ) { - TangemTheme { - InputRowBestRate( - imageUrl = "", - title = data.title, - titleExtra = data.titleExtra, - subtitle = data.subtitle, - showTag = data.showTag, - onIconClick = data.iconClick, - modifier = Modifier - .background(TangemTheme.colors.background.action), - ) - } -} - -@Preview -@Composable -private fun InputRowBestRatePreview_Dark( - @PreviewParameter(InputRowBestRatePreviewDataProvider::class) data: InputRowBestRatePreviewData, -) { - TangemTheme(isDark = true) { + TangemThemePreview { InputRowBestRate( imageUrl = "", title = data.title, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowDefault.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowDefault.kt index e41ac02d17..4222d238c0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowDefault.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowDefault.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.inputrow +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -19,6 +20,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.inputrow.inner.DividerContainer import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -94,27 +96,12 @@ fun InputRowDefault( //region preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun InputRowDefaultPreview_Light( +private fun InputRowDefaultPreview( @PreviewParameter(InputRowDefaultPreviewDataProvider::class) data: InputRowDefaultPreviewData, ) { - TangemTheme { - InputRowDefault( - title = TextReference.Str(data.title), - text = TextReference.Str(data.text), - iconRes = data.iconRes, - showDivider = data.showDivider, - modifier = Modifier.background(TangemTheme.colors.background.action), - ) - } -} - -@Preview -@Composable -private fun InputRowDefaultPreview_Dark( - @PreviewParameter(InputRowDefaultPreviewDataProvider::class) data: InputRowDefaultPreviewData, -) { - TangemTheme(isDark = true) { + TangemThemePreview { InputRowDefault( title = TextReference.Str(data.title), text = TextReference.Str(data.text), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnter.kt index f81372c3da..4d1490891f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnter.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.inputrow +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -22,6 +23,7 @@ import com.tangem.core.ui.components.inputrow.inner.DividerContainer import com.tangem.core.ui.components.fields.SimpleTextField import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -106,28 +108,12 @@ fun InputRowEnter( //region preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun InputRowEnterPreview_Light( +private fun InputRowEnterPreview( @PreviewParameter(InputRowEnterPreviewDataProvider::class) data: InputRowEnterPreviewData, ) { - TangemTheme { - InputRowEnter( - title = TextReference.Str(data.title), - text = data.text, - iconRes = data.iconRes, - showDivider = data.showDivider, - onValueChange = {}, - modifier = Modifier.background(TangemTheme.colors.background.action), - ) - } -} - -@Preview -@Composable -private fun InputRowEnterPreview_Dark( - @PreviewParameter(InputRowEnterPreviewDataProvider::class) data: InputRowEnterPreviewData, -) { - TangemTheme(isDark = true) { + TangemThemePreview { InputRowEnter( title = TextReference.Str(data.title), text = data.text, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfo.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfo.kt index 25debb16dc..c9e01fd85f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfo.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfo.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.inputrow +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardOptions @@ -16,6 +17,7 @@ import com.tangem.core.ui.components.inputrow.inner.DividerContainer import com.tangem.core.ui.components.fields.SimpleTextField import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -92,28 +94,12 @@ fun InputRowEnterInfo( //region preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun InputRowEnterInfoPreview_Light( +private fun InputRowEnterInfoPreview( @PreviewParameter(InputRowEnterInfoPreviewDataProvider::class) data: InputRowEnterInfoPreviewData, ) { - TangemTheme { - InputRowEnterInfo( - title = data.title, - text = data.text, - info = data.info, - showDivider = data.showDivider, - onValueChange = {}, - modifier = Modifier.background(TangemTheme.colors.background.action), - ) - } -} - -@Preview -@Composable -private fun InputRowEnterInfoPreview_Dark( - @PreviewParameter(InputRowEnterInfoPreviewDataProvider::class) data: InputRowEnterInfoPreviewData, -) { - TangemTheme(isDark = true) { + TangemThemePreview { InputRowEnterInfo( title = data.title, text = data.text, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImage.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImage.kt index 1754c3b8bc..35ba18ad8a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImage.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImage.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.inputrow +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -22,6 +23,7 @@ import com.tangem.core.ui.components.currency.tokenicon.TokenIcon import com.tangem.core.ui.components.currency.tokenicon.TokenIconState import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -120,31 +122,12 @@ fun InputRowImage( //region preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun InputRowInputEnterInfoPreview_Light( +private fun InputRowInputEnterInfoPreview( @PreviewParameter(InputRowImagePreviewDataProvider::class) data: InputRowImagePreviewData, ) { - TangemTheme { - InputRowImage( - title = data.title, - modifier = Modifier.background(TangemTheme.colors.background.action), - subtitle = data.subtitle, - caption = data.caption, - tokenIconState = data.iconState, - iconRes = data.actionIconRes, - onIconClick = {}, - showNetworkIcon = false, - showDivider = data.showDivider, - ) - } -} - -@Preview -@Composable -private fun InputRowImagePreview_Dark( - @PreviewParameter(InputRowImagePreviewDataProvider::class) data: InputRowImagePreviewData, -) { - TangemTheme(isDark = true) { + TangemThemePreview { InputRowImage( title = data.title, modifier = Modifier.background(TangemTheme.colors.background.action), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt index 41bbc546b7..de4873ae76 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.inputrow +import android.content.res.Configuration import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -22,6 +23,7 @@ import com.tangem.core.ui.components.inputrow.inner.DividerContainer import com.tangem.core.ui.components.inputrow.inner.PasteButton import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import kotlinx.coroutines.delay @@ -149,11 +151,12 @@ private fun RowScope.InputIcon(isLoading: Boolean, value: String) { //region preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun InputRowRecipientPreview_Light( +private fun InputRowRecipientPreview( @PreviewParameter(InputRowRecipientPreviewDataProvider::class) value: InputRowRecipientPreviewData, ) { - TangemTheme { + TangemThemePreview { InputRowRecipient( value = value.value, title = TextReference.Res(R.string.send_recipient), @@ -169,27 +172,6 @@ private fun InputRowRecipientPreview_Light( } } -@Preview -@Composable -private fun InputRowRecipientPreview_Dark( - @PreviewParameter(InputRowRecipientPreviewDataProvider::class) value: InputRowRecipientPreviewData, -) { - TangemTheme(isDark = true) { - InputRowRecipient( - value = value.value, - title = TextReference.Res(R.string.send_recipient), - placeholder = TextReference.Res(R.string.send_optional_field), - error = TextReference.Str("Error"), - isLoading = value.isLoading, - isError = value.isError, - showDivider = true, - onValueChange = {}, - onPasteClick = {}, - modifier = Modifier.background(TangemTheme.colors.background.primary), - ) - } -} - private data class InputRowRecipientPreviewData( val value: String, val isError: Boolean, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipientDefault.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipientDefault.kt index 8ae6b1b386..94f75c6a56 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipientDefault.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipientDefault.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.inputrow +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -15,6 +16,7 @@ import com.tangem.core.ui.components.icons.identicon.IdentIcon import com.tangem.core.ui.components.inputrow.inner.DividerContainer import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -79,9 +81,10 @@ fun InputRowRecipientDefault( //region preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun InputRowRecipientPreview_Light() { - TangemTheme { +private fun InputRowRecipientPreview() { + TangemThemePreview { InputRowRecipientDefault( value = "0x391316d97a07027a0702c8A002c8A0C25d8470", title = TextReference.Res(R.string.send_recipient), @@ -90,17 +93,4 @@ private fun InputRowRecipientPreview_Light() { ) } } - -@Preview -@Composable -private fun InputRowRecipientPreview_Dark() { - TangemTheme(isDark = true) { - InputRowRecipientDefault( - value = "0x391316d97a07027a0702c8A002c8A0C25d8470", - title = TextReference.Res(R.string.send_recipient), - showDivider = false, - modifier = Modifier.background(TangemTheme.colors.background.primary), - ) - } -} //endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt index 49c308e97e..c3538cb57d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.DEFAULT_ANIMATION_DURATION /** @@ -100,7 +101,7 @@ fun CrossIcon(onClick: (String) -> Unit, modifier: Modifier = Modifier) { @Composable private fun PasteButtonPreview() { var isVisible by remember { mutableStateOf(true) } - TangemTheme { + TangemThemePreview { PasteButton( isPasteButtonVisible = isVisible, onClick = { isVisible = !isVisible }, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt index 873d6dbcb2..606416093a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.marketprice +import android.content.res.Configuration import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -19,6 +20,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import androidx.compose.ui.unit.Dp import com.tangem.core.ui.R import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter @@ -203,23 +205,13 @@ private fun QuoteTimeStatus() { // region Preview @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_MarketPriceBlock_Light( +private fun Preview_MarketPriceBlock( @PreviewParameter(WalletMarketPriceBlockStateProvider::class) state: MarketPriceBlockState, ) { - TangemTheme(isDark = false) { - MarketPriceBlock(state = state) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun Preview_MarketPriceBlock_Dark( - @PreviewParameter(WalletMarketPriceBlockStateProvider::class) - state: MarketPriceBlockState, -) { - TangemTheme(isDark = true) { + TangemThemePreview { MarketPriceBlock(state = state) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt index c352cf41eb..6d339e48dd 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.notifications +import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image @@ -29,6 +30,7 @@ import com.tangem.core.ui.components.* import com.tangem.core.ui.components.buttons.common.TangemButtonSize import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState as NotificationButtonsState @@ -269,22 +271,13 @@ private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Mod } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_Notification_Light( +private fun Preview_Notification( @PreviewParameter(NotificationConfigProvider::class) config: NotificationConfig, ) { - TangemTheme(isDark = false) { - Notification(config) - } -} - -@Preview -@Composable -private fun Preview_Notification_Dark( - @PreviewParameter(NotificationConfigProvider::class) config: NotificationConfig, -) { - TangemTheme(isDark = true) { + TangemThemePreview { Notification(config) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt index e4e8a06833..00945dbb44 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.notifications +import android.content.res.Configuration import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -32,6 +33,7 @@ import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.LocalIsInDarkTheme import com.tangem.core.ui.res.TangemColorPalette.Dark6 import com.tangem.core.ui.res.TangemColorPalette.Light4 +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -149,21 +151,12 @@ fun NotificationWithBackground(config: NotificationConfig, modifier: Modifier = //region preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun NotificationWithBackgroundPreview_Light( +private fun NotificationWithBackgroundPreview( @PreviewParameter(NotificationWithBackgroundPreviewProvider::class) config: NotificationConfig, ) { - TangemTheme { - NotificationWithBackground(config = config) - } -} - -@Preview -@Composable -private fun NotificationWithBackgroundPreview_Dark( - @PreviewParameter(NotificationWithBackgroundPreviewProvider::class) config: NotificationConfig, -) { - TangemTheme(isDark = true) { + TangemThemePreview { NotificationWithBackground(config = config) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/ActionRow.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/ActionRow.kt index 0c2a785acc..36c44d81b3 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/ActionRow.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/ActionRow.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.components.SpacerH28 import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview /** * Simple clickable action row, without input and icon @@ -66,7 +67,7 @@ fun SimpleActionRow(title: String, description: String, modifier: Modifier = Mod @Composable private fun SimpleActionRowPreview() { Column { - TangemTheme(isDark = false) { + TangemThemePreview(isDark = false) { SimpleActionRow( title = "Title", description = "Description", @@ -75,7 +76,7 @@ private fun SimpleActionRowPreview() { SpacerH28() - TangemTheme(isDark = false) { + TangemThemePreview(isDark = false) { SimpleActionRow( title = "Title", description = "Description", diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt index b919363574..6795d2e0f3 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.rows +import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.animation.animateColorAsState @@ -21,6 +22,7 @@ import com.tangem.core.ui.components.atoms.text.EllipsisText import com.tangem.core.ui.components.atoms.text.TextEllipsis import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme @Composable @@ -132,25 +134,10 @@ private fun RowScope.SelectorValueContent( } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun SelectorRowItemPreview_Light() { - TangemTheme { - SelectorRowItem( - titleRes = R.string.common_fee_selector_option_slow, - iconRes = R.drawable.ic_tortoise_24, - preDot = TextReference.Str("1000 ETH"), - postDot = TextReference.Str("1000 $"), - ellipsizeOffset = 4, - isSelected = true, - onSelect = { }, - ) - } -} - -@Preview -@Composable -private fun SelectorRowItemPreview_Dark() { - TangemTheme(isDark = true) { +private fun SelectorRowItemPreview() { + TangemThemePreview { SelectorRowItem( titleRes = R.string.common_fee_selector_option_slow, iconRes = R.drawable.ic_tortoise_24, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt index 271e85f311..3c76514a21 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.transactions +import android.content.res.Configuration import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -32,6 +33,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import java.util.UUID @@ -318,21 +320,10 @@ private fun LockedContent(modifier: Modifier = Modifier) { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_TransactionItem_LightTheme( - @PreviewParameter(TransactionItemStateProvider::class) state: TransactionState, -) { - TangemTheme(isDark = false) { - Transaction(state = state, isBalanceHidden = false) - } -} - -@Preview -@Composable -private fun Preview_TransactionItem_DarkTheme( - @PreviewParameter(TransactionItemStateProvider::class) state: TransactionState, -) { - TangemTheme(isDark = true) { +private fun Preview_TransactionItem(@PreviewParameter(TransactionItemStateProvider::class) state: TransactionState) { + TangemThemePreview { Transaction(state = state, isBalanceHidden = false) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt index 6f23701fea..8a7b6bd599 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.transactions +import android.content.res.Configuration import androidx.annotation.StringRes import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -15,6 +16,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.core.ui.utils.toTimeFormat @@ -62,23 +64,10 @@ fun TransactionDoneTitle(@StringRes titleRes: Int, date: Long, modifier: Modifie // region Previews @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun TransactionDoneTitlePreview_Light() { - TangemTheme { - TransactionDoneTitle( - titleRes = R.string.sent_transaction_sent_title, - date = 0, - modifier = Modifier - .background(TangemTheme.colors.background.tertiary) - .padding(TangemTheme.dimens.spacing16), - ) - } -} - -@Preview -@Composable -private fun TransactionDoneTitlePreview_Dark() { - TangemTheme(isDark = true) { +private fun TransactionDoneTitlePreview() { + TangemThemePreview { TransactionDoneTitle( titleRes = R.string.sent_transaction_sent_title, date = 0, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryGroupTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryGroupTitle.kt index b2b02d9e1a..e0298ceaed 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryGroupTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryGroupTitle.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.transactions +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -11,6 +12,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import java.util.UUID @@ -42,19 +44,10 @@ internal fun TxHistoryGroupTitle(config: TxHistoryItemState.GroupTitle, modifier } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_TransactionsBlockGroupTitle_Light() { - TangemTheme(isDark = false) { - TxHistoryGroupTitle( - config = TxHistoryItemState.GroupTitle(title = "Today", itemKey = UUID.randomUUID().toString()), - ) - } -} - -@Preview -@Composable -private fun Preview_TransactionsBlockGroupTitle_Dark() { - TangemTheme(isDark = true) { +private fun Preview_TransactionsBlockGroupTitle() { + TangemThemePreview { TxHistoryGroupTitle( config = TxHistoryItemState.GroupTitle(title = "Today", itemKey = UUID.randomUUID().toString()), ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt index 352c19d181..3a7f89969c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.transactions +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -12,6 +13,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -59,17 +61,10 @@ internal fun TxHistoryTitle(onExploreClick: () -> Unit, modifier: Modifier = Mod } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_TransactionsBlockTitle_Light() { - TangemTheme(isDark = false) { - TxHistoryTitle(onExploreClick = {}) - } -} - -@Preview -@Composable -private fun Preview_TransactionsBlockTitle_Dark() { - TangemTheme(isDark = true) { +private fun Preview_TransactionsBlockTitle() { + TangemThemePreview { TxHistoryTitle(onExploreClick = {}) } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/empty/EmptyTransactionBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/empty/EmptyTransactionBlock.kt index c2b7368924..a6d96d536d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/empty/EmptyTransactionBlock.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/empty/EmptyTransactionBlock.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.transactions.empty +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.Icon @@ -15,6 +16,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.components.buttons.actions.ActionButton import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme /** @@ -87,20 +89,11 @@ private fun PairButtons(state: EmptyTransactionsBlockState.ButtonsState.PairButt @Composable @Preview(widthDp = 360, showBackground = true) -private fun EmptyTransactionBlock_Light( +@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun EmptyTransactionBlockPreview( @PreviewParameter(EmptyTransactionBlockStateProvider::class) state: EmptyTransactionsBlockState, ) { - TangemTheme { - EmptyTransactionBlock(state = state) - } -} - -@Composable -@Preview(widthDp = 360, showBackground = true) -private fun EmptyTransactionBlock_Dark( - @PreviewParameter(EmptyTransactionBlockStateProvider::class) state: EmptyTransactionsBlockState, -) { - TangemTheme(isDark = true) { + TangemThemePreview { EmptyTransactionBlock(state = state) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt new file mode 100644 index 0000000000..2a9e0caf65 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt @@ -0,0 +1,21 @@ +package com.tangem.core.ui.res + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable + +@Composable +fun TangemThemePreview( + isDark: Boolean? = null, + typography: TangemTypography = TangemTheme.typography, + dimens: TangemDimens = TangemTheme.dimens, + content: @Composable () -> Unit, +) { + val isDarkTheme = isDark ?: isSystemInDarkTheme() + + TangemTheme( + isDark = isDarkTheme, + typography = typography, + dimens = dimens, + content = content, + ) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/AddCustomTokenScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/AddCustomTokenScreen.kt index d335aaca71..d5691e9017 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/AddCustomTokenScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/AddCustomTokenScreen.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.addcustomtoken.ui +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -24,6 +25,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.managetokens.impl.R import com.tangem.managetokens.presentation.common.state.AlertState import com.tangem.managetokens.presentation.common.state.ChooseWalletState @@ -287,17 +289,10 @@ private fun TokenTextFieldTitle(state: TextFieldState?, title: String) { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_ChooseDerivationScreen_Light() { - TangemTheme(isDark = false) { - AddCustomTokenScreen(state = AddCustomTokenPreviewData.state) - } -} - -@Preview -@Composable -private fun Preview_ChooseDerivationScreen_Dark() { - TangemTheme(isDark = true) { +private fun Preview_ChooseDerivationScreen() { + TangemThemePreview { AddCustomTokenScreen(state = AddCustomTokenPreviewData.state) } } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseDerivationScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseDerivationScreen.kt index 3422e1cf8e..a7805cbb96 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseDerivationScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseDerivationScreen.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.addcustomtoken.ui +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -13,6 +14,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.decorations.roundedShapeItemDecoration +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.features.managetokens.impl.R import com.tangem.managetokens.presentation.common.ui.components.SimpleSelectionBlock @@ -98,17 +100,10 @@ private fun DerivationsList(state: ChooseDerivationState) { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_ChooseDerivationScreen_Light() { - TangemTheme(isDark = false) { - ChooseDerivationScreen(state = ChooseDerivationPreviewData.state) - } -} - -@Preview -@Composable -private fun Preview_ChooseDerivationScreen_Dark() { - TangemTheme(isDark = true) { +private fun Preview_ChooseDerivationScreen() { + TangemThemePreview { ChooseDerivationScreen(state = ChooseDerivationPreviewData.state) } } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseNetworkCustomScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseNetworkCustomScreen.kt index 4ee903652e..6777fc4fbc 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseNetworkCustomScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/ChooseNetworkCustomScreen.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.addcustomtoken.ui +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -13,6 +14,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.decorations.roundedShapeItemDecoration +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.features.managetokens.impl.R import com.tangem.managetokens.presentation.common.ui.components.NetworkItem @@ -78,17 +80,10 @@ internal fun ChooseNetworkCustomScreen(state: ChooseNetworkState, modifier: Modi } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_ChooseNetworkScreen_Light() { - TangemTheme(isDark = false) { - ChooseNetworkCustomScreen(ChooseNetworkCustomPreviewData.state) - } -} - -@Preview -@Composable -private fun Preview_ChooseNetworkScreen_Dark() { - TangemTheme(isDark = true) { +private fun Preview_ChooseNetworkScreen() { + TangemThemePreview { ChooseNetworkCustomScreen(ChooseNetworkCustomPreviewData.state) } } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt index 878a5f28e1..121faaba26 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.common.ui +import android.content.res.Configuration import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -23,6 +24,7 @@ import com.tangem.core.ui.components.SpacerW12 import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.managetokens.impl.R import com.tangem.managetokens.presentation.common.state.ChooseWalletState import com.tangem.managetokens.presentation.common.state.WalletState @@ -137,19 +139,10 @@ private fun WalletItem(wallet: WalletState, selectedWallet: WalletState?, modifi } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_ChooseWalletScreen_Light() { - TangemTheme(isDark = false) { - ChooseWalletScreen( - state = ChooseWalletStatePreviewData.state, - ) - } -} - -@Preview -@Composable -private fun Preview_ChooseWalletScreen_Dark() { - TangemTheme(isDark = false) { +private fun Preview_ChooseWalletScreen() { + TangemThemePreview { ChooseWalletScreen( state = ChooseWalletStatePreviewData.state, ) diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/NetworkItem.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/NetworkItem.kt index 0359b28aa1..553a6bd999 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/NetworkItem.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/NetworkItem.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.common.ui.components +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -18,6 +19,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.components.SpacerW import com.tangem.core.ui.components.TangemSwitch +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.features.managetokens.impl.R import com.tangem.managetokens.presentation.common.state.NetworkItemState @@ -122,17 +124,10 @@ internal fun NetworkIcon(model: NetworkItemState, modifier: Modifier = Modifier) } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_NetworkItem_Light(@PreviewParameter(NetworkItemStateProvider::class) state: NetworkItemState) { - TangemTheme(isDark = false) { - NetworkItem(state, tokenState = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded) - } -} - -@Preview -@Composable -private fun Preview_NetworkItem_Dark(@PreviewParameter(NetworkItemStateProvider::class) state: NetworkItemState) { - TangemTheme(isDark = true) { +private fun Preview_NetworkItem(@PreviewParameter(NetworkItemStateProvider::class) state: NetworkItemState) { + TangemThemePreview { NetworkItem(state, tokenState = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded) } } diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/SimpleSelectionBlock.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/SimpleSelectionBlock.kt index c136cb1e28..e7ad5cf31b 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/SimpleSelectionBlock.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/SimpleSelectionBlock.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.common.ui.components +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -12,6 +13,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme @Composable @@ -54,17 +56,10 @@ fun SimpleSelectionBlock( } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_SimpleSelectionBlock_Light() { - TangemTheme(isDark = false) { - SimpleSelectionBlock(title = "Wallet", subtitle = "Family Wallet", onClick = { }) - } -} - -@Preview -@Composable -private fun Preview_SimpleSelectionBlock_Dark() { - TangemTheme(isDark = true) { +private fun Preview_SimpleSelectionBlock() { + TangemThemePreview { SimpleSelectionBlock(title = "Wallet", subtitle = "Family Wallet", onClick = { }) } } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt index ead0700da2..2f5d51df8a 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.managetokens.ui +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -17,6 +18,7 @@ import com.tangem.core.ui.components.SpacerH import com.tangem.core.ui.components.SpacerW import com.tangem.core.ui.components.WarningCardTitleOnly import com.tangem.core.ui.decorations.roundedShapeItemDecoration +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.features.managetokens.impl.R import com.tangem.managetokens.presentation.common.state.ChooseWalletState @@ -187,20 +189,10 @@ private fun NonNativeNetworksHeader(onNonNativeNetworkHintClick: () -> Unit) { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_ChooseNetworkScreen_Light() { - TangemTheme(isDark = false) { - ChooseNetworkScreen( - state = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded, - walletState = ChooseWalletStatePreviewData.state, - ) - } -} - -@Preview -@Composable -private fun Preview_ChooseNetworkScreen_Dark() { - TangemTheme(isDark = true) { +private fun Preview_ChooseNetworkScreen() { + TangemThemePreview { ChooseNetworkScreen( state = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded, walletState = ChooseWalletStatePreviewData.state, diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt index 12f7a0a191..15197a8e4a 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.managetokens.ui +import android.content.res.Configuration import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -22,6 +23,7 @@ import com.tangem.core.ui.components.Keyboard import com.tangem.core.ui.components.SpacerH18 import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.keyboardAsState +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.managetokens.presentation.addcustomtoken.ui.AddCustomTokenBottomSheet import com.tangem.managetokens.presentation.common.state.AlertState @@ -177,23 +179,13 @@ private fun ManageTokensBottomSheet(selectedToken: TokenItemState.Loaded, state: } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_ManageTokensScreen_LightTheme( +private fun Preview_ManageTokensScreen( @PreviewParameter(ManageTokensConfigProvider::class) state: ManageTokensState, ) { - TangemTheme(isDark = false) { - ManageTokensScreen(state) {} - } -} - -@Preview -@Composable -private fun Preview_ManageTokensScreen_DarkTheme( - @PreviewParameter(ManageTokensConfigProvider::class) - state: ManageTokensState, -) { - TangemTheme(isDark = true) { + TangemThemePreview { ManageTokensScreen(state) {} } } diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/AddCustomTokenButton.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/AddCustomTokenButton.kt index 195bb4d60c..32962c70db 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/AddCustomTokenButton.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/AddCustomTokenButton.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.managetokens.ui.components +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -14,6 +15,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.SpacerW import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.managetokens.impl.R @Composable @@ -49,17 +51,10 @@ internal fun AddCustomTokenButton(onButtonClick: () -> Unit, modifier: Modifier } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun AddCustomTokenButton_Preview_Light() { - TangemTheme(isDark = false) { - AddCustomTokenButton(onButtonClick = { }) - } -} - -@Preview -@Composable -private fun AddCustomTokenButton_Preview_Dark() { - TangemTheme(isDark = true) { +private fun AddCustomTokenButton_Preview() { + TangemThemePreview { AddCustomTokenButton(onButtonClick = { }) } } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/DerivationNotification.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/DerivationNotification.kt index a18fffaf56..d1f3822c9b 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/DerivationNotification.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/DerivationNotification.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.managetokens.ui.components +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape @@ -18,6 +19,7 @@ import com.tangem.core.ui.components.SpacerW import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.features.managetokens.impl.R import com.tangem.managetokens.presentation.managetokens.state.previewdata.DerivationNotificationStatePreviewData @@ -127,17 +129,10 @@ private fun TextsBlock(title: TextReference, subtitle: TextReference) { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_ManageTokensScreen_LightTheme() { - TangemTheme(isDark = false) { - DerivationNotification(DerivationNotificationStatePreviewData.state.config) - } -} - -@Preview -@Composable -private fun Preview_ManageTokensScreen_DarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_ManageTokensScreen() { + TangemThemePreview { DerivationNotification(DerivationNotificationStatePreviewData.state.config) } } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/PriceChangesChart.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/PriceChangesChart.kt index 998dbebc72..1a18c5ee93 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/PriceChangesChart.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/PriceChangesChart.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -105,7 +106,7 @@ private fun getValuePercentageForRange(value: Float, max: Float, min: Float): Fl @Preview(widthDp = 150, heightDp = 150, showBackground = true) @Composable private fun Chart_Positive_Preview() { - TangemTheme(isDark = true) { + TangemThemePreview(isDark = true) { PriceChangesChart( persistentListOf(1f, 2f, 4f, 1f, 5f), ) @@ -115,7 +116,7 @@ private fun Chart_Positive_Preview() { @Preview(widthDp = 150, heightDp = 150, showBackground = true) @Composable private fun Chart_Negative_Preview() { - TangemTheme(isDark = true) { + TangemThemePreview(isDark = true) { PriceChangesChart( persistentListOf(10f, 2f, 4f, 1f, 5f), ) @@ -125,7 +126,7 @@ private fun Chart_Negative_Preview() { @Preview(widthDp = 150, heightDp = 150, showBackground = true) @Composable private fun Chart_Neutral_Preview() { - TangemTheme(isDark = true) { + TangemThemePreview(isDark = true) { PriceChangesChart( persistentListOf(5f, 2f, 4f, 1f, 5f), ) diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/SearchBar.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/SearchBar.kt index 6040c96260..8faf911ebb 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/SearchBar.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/SearchBar.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.managetokens.ui.components +import android.content.res.Configuration import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.shape.RoundedCornerShape @@ -20,6 +21,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.features.managetokens.impl.R import com.tangem.managetokens.presentation.managetokens.state.SearchBarState @@ -107,20 +109,13 @@ private fun searchbarTextFieldColors(): TextFieldColors { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_TokensSearchBar_Light( +private fun Preview_TokensSearchBar( @PreviewParameter(SearchBarkConfigProvider::class) state: SearchBarState, ) { - TangemTheme(isDark = false) { - TokensSearchBar(state) - } -} - -@Preview -@Composable -private fun Preview_TokensSearchBar_Dark(@PreviewParameter(SearchBarkConfigProvider::class) state: SearchBarState) { - TangemTheme(isDark = true) { + TangemThemePreview { TokensSearchBar(state) } } diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenButton.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenButton.kt index f3f76effd3..3829ec91db 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenButton.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenButton.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.managetokens.ui.components +import android.content.res.Configuration import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box @@ -18,6 +19,7 @@ import com.tangem.core.ui.components.buttons.PrimarySmallButton import com.tangem.core.ui.components.buttons.SecondarySmallButton import com.tangem.core.ui.components.buttons.SmallButtonConfig import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.features.managetokens.impl.R import com.tangem.managetokens.presentation.managetokens.state.TokenButtonType @@ -66,17 +68,10 @@ internal fun TokenButton(type: TokenButtonType, onClick: () -> Unit, modifier: M } @Preview(backgroundColor = 0xffffff, showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun TokenButton_Light_Preview(@PreviewParameter(TokenButtonTypeProvider::class) type: TokenButtonType) { - TangemTheme(isDark = false) { - TokenButton(type = type, {}) - } -} - -@Preview(showBackground = true) -@Composable -private fun TokenButton_Dark_Preview(@PreviewParameter(TokenButtonTypeProvider::class) type: TokenButtonType) { - TangemTheme(isDark = true) { +private fun TokenButton_Preview(@PreviewParameter(TokenButtonTypeProvider::class) type: TokenButtonType) { + TangemThemePreview { TokenButton(type = type, {}) } } diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenRowItem.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenRowItem.kt index d92f0a8b81..50cf504643 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenRowItem.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenRowItem.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.managetokens.ui.components +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.Surface @@ -14,6 +15,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.Dp import com.tangem.core.ui.components.* +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.managetokens.presentation.managetokens.state.QuotesState import com.tangem.managetokens.presentation.managetokens.state.TokenItemState @@ -182,17 +184,10 @@ private fun BaseSurface(modifier: Modifier = Modifier, content: @Composable () - // region Preview @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_Tokens_LightTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) { - TangemTheme(isDark = false) { - TokenRowItem(state) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun Preview_Tokens_DarkTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) { - TangemTheme(isDark = true) { +private fun Preview_Tokens(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) { + TangemThemePreview { TokenRowItem(state) } } diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index abc2bfff70..de2ffddcbc 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -1,5 +1,6 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui +import android.content.res.Configuration import androidx.compose.animation.* import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -21,6 +22,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import com.tangem.core.ui.components.* +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.R import com.tangem.feature.onboarding.presentation.wallet2.model.ButtonState @@ -204,11 +206,12 @@ private fun Modifier.rowPadding(index: Int, rowSize: Int, outSide: Dp, inSide: D } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun SuggestionsBlockPreview_Light( +private fun SuggestionsBlockPreview( @PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList, ) { - TangemTheme(isDark = false) { + TangemThemePreview { SuggestionsBlock( suggestionsList = suggestions, onClick = {}, @@ -217,24 +220,12 @@ private fun SuggestionsBlockPreview_Light( } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun SuggestionsBlockPreview_Dark( +private fun ImportSeedPhraseScreenPreview( @PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList, ) { - TangemTheme(isDark = true) { - SuggestionsBlock( - suggestionsList = suggestions, - onClick = {}, - ) - } -} - -@Preview -@Composable -private fun ImportSeedPhraseScreenPreview_Light( - @PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList, -) { - TangemTheme(isDark = false) { + TangemThemePreview { Box(modifier = Modifier.background(TangemTheme.colors.background.primary)) { ImportSeedPhraseScreen( ImportSeedPhraseState( diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt index ee04bfe228..9228432a75 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt @@ -18,6 +18,7 @@ import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.onboarding.R import com.tangem.feature.onboarding.presentation.wallet2.model.ShowPassphraseInfoBottomSheetContent @@ -83,7 +84,7 @@ fun PassphraseInfoBottomSheetContent(content: ShowPassphraseInfoBottomSheetConte @Preview @Composable private fun PassphraseInfoBottomSheetContentPreview() { - TangemTheme { + TangemThemePreview { PassphraseInfoBottomSheetContent(ShowPassphraseInfoBottomSheetContent { }) } } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt index 609e2df784..9fafacb32a 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt @@ -1,5 +1,6 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -16,6 +17,7 @@ import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.onboarding.R import com.tangem.feature.onboarding.presentation.wallet2.model.* import com.tangem.feature.onboarding.presentation.wallet2.ui.components.DescriptionSubTitleText @@ -164,9 +166,10 @@ private inline fun VerticalGrid( } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun YourSeedPhraseScreenPreview_Light() { - TangemTheme(isDark = false) { + TangemThemePreview { YourSeedPhraseScreen( state = YourSeedPhraseState( segmentSeedState = SegmentSeedState( diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt index bc175c5bff..e44e192d0d 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt @@ -1,5 +1,6 @@ package com.tangem.feature.referral.ui +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -16,6 +17,7 @@ import androidx.compose.ui.unit.dp import com.google.accompanist.web.WebView import com.google.accompanist.web.rememberWebViewState import com.tangem.core.ui.components.appbar.AppBarWithAdditionalButtons +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.referral.presentation.R @@ -58,17 +60,10 @@ private fun AgreementHtmlView(url: String) { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_AgreementBottomSheet_InLightTheme() { - TangemTheme(isDark = false) { - AgreementBottomSheetContent(url = "https://tangem.com/en/") - } -} - -@Preview -@Composable -private fun Preview_AgreementBottomSheet_InDarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_AgreementBottomSheet() { + TangemThemePreview { AgreementBottomSheetContent(url = "https://tangem.com/en/") } } \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt index d29a6c996b..e005f91c7b 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt @@ -1,5 +1,6 @@ package com.tangem.feature.referral.ui +import android.content.res.Configuration import androidx.annotation.StringRes import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -15,6 +16,7 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.referral.presentation.R @@ -53,19 +55,10 @@ private fun annotatedAgreementString(firstPart: String): AnnotatedString { } @Preview(widthDp = 360, showBackground = true) +@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_AgreementText_InLightTheme() { - TangemTheme(isDark = false) { - Box(modifier = Modifier.background(TangemTheme.colors.background.primary)) { - AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClick = {}) - } - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -private fun Preview_AgreementText_InDarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_AgreementText() { + TangemThemePreview { Box(modifier = Modifier.background(TangemTheme.colors.background.primary)) { AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClick = {}) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AwardItems.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AwardItems.kt index a85c1a0f07..5f11c074e1 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AwardItems.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AwardItems.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview @Suppress("LongParameterList") @Composable @@ -56,8 +57,8 @@ internal fun AwardText( @Preview(widthDp = 360, showBackground = true) @Composable -private fun Preview_AwardItem_Light() { - TangemTheme { +private fun Preview_AwardItem() { + TangemThemePreview { AwardText( startText = "startText", startTextColor = TangemTheme.colors.text.tertiary, diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt index 78a8239dc0..d79e4a6f6b 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt @@ -1,5 +1,6 @@ package com.tangem.feature.referral.ui +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding @@ -8,6 +9,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryEndIconButton +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.referral.presentation.R @@ -28,19 +30,10 @@ internal fun NonParticipateBottomBlock(onAgreementClick: () -> Unit, onParticipa } @Preview(widthDp = 360, showBackground = true) +@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_NonParticipateBottomBlock_InLightTheme() { - TangemTheme(isDark = false) { - Column(Modifier.background(TangemTheme.colors.background.primary)) { - NonParticipateBottomBlock(onAgreementClick = {}, onParticipateClick = {}) - } - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -private fun Preview_NonParticipateBottomBlock_InDarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_NonParticipateBottomBlock() { + TangemThemePreview { Column(Modifier.background(TangemTheme.colors.background.primary)) { NonParticipateBottomBlock(onAgreementClick = {}, onParticipateClick = {}) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index 3417b6931f..69c37e25c9 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -2,6 +2,7 @@ package com.tangem.feature.referral.ui import android.content.Context import android.content.Intent +import android.content.res.Configuration import androidx.compose.animation.* import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -31,6 +32,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.core.content.ContextCompat.startActivity import com.tangem.core.ui.components.* +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.referral.domain.models.ExpectedAward import com.tangem.feature.referral.domain.models.ExpectedAwards @@ -318,11 +320,12 @@ private fun Context.shareText(text: String) { } @Preview(widthDp = 360, showBackground = true) +@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun ParticipateBottomBlockPreview_Light( +private fun ParticipateBottomBlockPreview( @PreviewParameter(ParticipateBottomBlockDataProvider::class) data: ParticipateBottomBlockData, ) { - TangemTheme(isDark = false) { + TangemThemePreview { Column(Modifier.background(TangemTheme.colors.background.secondary)) { ParticipateBottomBlock( purchasedWalletCount = data.purchasedWalletCount, @@ -339,42 +342,10 @@ private fun ParticipateBottomBlockPreview_Light( } @Preview(widthDp = 360, showBackground = true) +@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun ParticipateBottomBlockPreview_Dark( - @PreviewParameter(ParticipateBottomBlockDataProvider::class) state: ParticipateBottomBlockData, -) { - TangemTheme(isDark = true) { - Column(Modifier.background(TangemTheme.colors.background.secondary)) { - ParticipateBottomBlock( - purchasedWalletCount = state.purchasedWalletCount, - code = state.code, - shareLink = state.shareLink, - expectedAwards = state.expectedAwards, - onAgreementClick = state.onAgreementClick, - onShowCopySnackbar = state.onShowCopySnackbar, - onCopyClick = state.onCopyClick, - onShareClick = state.onShareClick, - ) - } - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -private fun LessMoreButton_Light() { - TangemTheme(isDark = false) { - LessMoreButton( - isExpanded = remember { - mutableStateOf(false) - }, - ) - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -private fun LessMoreButton_Dark() { - TangemTheme(isDark = true) { +private fun LessMoreButtonPreview() { + TangemThemePreview { LessMoreButton( isExpanded = remember { mutableStateOf(false) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 74f9653422..cdff31bf58 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -1,5 +1,6 @@ package com.tangem.feature.referral.ui +import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -27,6 +28,7 @@ import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.referral.domain.models.ExpectedAward import com.tangem.feature.referral.domain.models.ExpectedAwards @@ -446,9 +448,10 @@ private fun getMessageForErrorSnackbar(errorSnackbar: ErrorSnackbar): String { } @Preview(widthDp = 360, showBackground = true) +@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_ReferralScreen_Participant_InLightTheme() { - TangemTheme(isDark = false) { +private fun Preview_ReferralScreen_Participant() { + TangemThemePreview { ReferralScreen( stateHolder = ReferralStateHolder( headerState = HeaderState(onBackClicked = {}), @@ -475,38 +478,10 @@ private fun Preview_ReferralScreen_Participant_InLightTheme() { } @Preview(widthDp = 360, showBackground = true) +@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_ReferralScreen_Participant_InDarkTheme() { - TangemTheme(isDark = true) { - ReferralScreen( - stateHolder = ReferralStateHolder( - headerState = HeaderState(onBackClicked = {}), - referralInfoState = ReferralInfoState.ParticipantContent( - award = "10 USDT", - networkName = "Tron", - address = "ma80...zk8q2", - discount = "10%", - purchasedWalletCount = 3, - code = "x4JdK", - shareLink = "", - url = "", - expectedAwards = null, - ), - errorSnackbar = ErrorSnackbar(DemoModeException()) {}, - analytics = Analytics( - onAgreementClicked = {}, - onCopyClicked = {}, - onShareClicked = {}, - ), - ), - ) - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -private fun Preview_ReferralScreen_Participant_With_Referrals_InLightTheme() { - TangemTheme(isDark = false) { +private fun Preview_ReferralScreen_Participant_With_Referrals() { + TangemThemePreview { ReferralScreen( stateHolder = ReferralStateHolder( headerState = HeaderState(onBackClicked = {}), @@ -549,9 +524,10 @@ private fun Preview_ReferralScreen_Participant_With_Referrals_InLightTheme() { } @Preview(widthDp = 360, showBackground = true) +@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_ReferralScreen_NonParticipant_InLightTheme() { - TangemTheme(isDark = false) { +private fun Preview_ReferralScreen_NonParticipant() { + TangemThemePreview { ReferralScreen( stateHolder = ReferralStateHolder( headerState = HeaderState(onBackClicked = {}), @@ -574,53 +550,10 @@ private fun Preview_ReferralScreen_NonParticipant_InLightTheme() { } @Preview(widthDp = 360, showBackground = true) +@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_ReferralScreen_NonParticipant_InDarkTheme() { - TangemTheme(isDark = true) { - ReferralScreen( - stateHolder = ReferralStateHolder( - headerState = HeaderState(onBackClicked = {}), - referralInfoState = ReferralInfoState.NonParticipantContent( - award = "10 USDT", - networkName = "Tron", - discount = "10%", - url = "", - onParticipateClicked = {}, - ), - errorSnackbar = null, - analytics = Analytics( - onAgreementClicked = {}, - onCopyClicked = {}, - onShareClicked = {}, - ), - ), - ) - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -private fun Preview_ReferralScreen_Loading_InLightTheme() { - TangemTheme(isDark = false) { - ReferralScreen( - stateHolder = ReferralStateHolder( - headerState = HeaderState(onBackClicked = {}), - referralInfoState = ReferralInfoState.Loading, - errorSnackbar = null, - analytics = Analytics( - onAgreementClicked = {}, - onCopyClicked = {}, - onShareClicked = {}, - ), - ), - ) - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -private fun Preview_ReferralScreen_Loading_InDarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_ReferralScreen_Loading() { + TangemThemePreview { ReferralScreen( stateHolder = ReferralStateHolder( headerState = HeaderState(onBackClicked = {}), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt index 01d23d4edb..0c51b1dd4f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.ui.amount +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -8,6 +9,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.previewdata.AmountStatePreviewData @@ -43,25 +45,12 @@ internal fun SendAmountContent( // region Preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun AmountFieldPreview_Light( +private fun AmountFieldPreview( @PreviewParameter(AmountFieldPreviewProvider::class) amountState: SendStates.AmountState, ) { - TangemTheme { - SendAmountContent( - amountState = amountState, - isBalanceHiding = false, - clickIntents = SendClickIntentsStub, - ) - } -} - -@Preview -@Composable -private fun AmountFieldPreview_Dark( - @PreviewParameter(AmountFieldPreviewProvider::class) amountState: SendStates.AmountState, -) { - TangemTheme(isDark = true) { + TangemThemePreview { SendAmountContent( amountState = amountState, isBalanceHiding = false, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt index 270d4954f9..f403cf423b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.ui.fee +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -18,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fee.FeeType @@ -107,21 +109,12 @@ private fun FooterText(onReadMoreClick: () -> Unit) { // region Preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun SendSpeedSelectorPreview_Light( +private fun SendSpeedSelectorPreview( @PreviewParameter(SendSpeedSelectorPreviewProvider::class) feeState: SendStates.FeeState, ) { - TangemTheme { - SendSpeedSelector(state = feeState, clickIntents = SendClickIntentsStub) - } -} - -@Preview -@Composable -private fun SendSpeedSelectorPreview_Dark( - @PreviewParameter(SendSpeedSelectorPreviewProvider::class) feeState: SendStates.FeeState, -) { - TangemTheme(isDark = true) { + TangemThemePreview { SendSpeedSelector(state = feeState, clickIntents = SendClickIntentsStub) } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt index 9ef9f60a21..3f2106d79d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.ui.recipient +import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn @@ -27,6 +28,7 @@ import com.tangem.core.ui.components.atoms.text.EllipsisText import com.tangem.core.ui.components.atoms.text.TextEllipsis import com.tangem.core.ui.components.icons.identicon.IdentIcon import com.tangem.core.ui.extensions.rememberHapticFeedback +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.R @@ -180,28 +182,12 @@ private fun ListItemLoading(modifier: Modifier = Modifier) { // region preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun ListItemWithIconPreview_Light( +private fun ListItemWithIconPreview( @PreviewParameter(ListItemWithIconPreviewProvider::class) config: ListItemWithIconPreviewConfig, ) { - TangemTheme { - ListItemWithIcon( - title = config.title, - subtitle = config.subtitle, - subtitleEndOffset = config.subtitleEndOffset, - subtitleIconRes = config.iconRes, - onClick = {}, - isLoading = config.isLoading, - ) - } -} - -@Preview -@Composable -private fun ListItemWithIconPreview_Dark( - @PreviewParameter(ListItemWithIconPreviewProvider::class) config: ListItemWithIconPreviewConfig, -) { - TangemTheme(isDark = true) { + TangemThemePreview { ListItemWithIcon( title = config.title, subtitle = config.subtitle, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt index ab68759908..3533b2a175 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.ui.send +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -16,6 +17,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.core.ui.components.ResizableText import com.tangem.core.ui.components.currency.tokenicon.TokenIcon +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.send.impl.presentation.state.SendStates @@ -84,11 +86,10 @@ internal fun AmountBlock( // region Preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun AmountBlockPreview_Light( - @PreviewParameter(AmountBlockPreviewProvider::class) value: SendStates.AmountState, -) { - TangemTheme { +private fun AmountBlockPreview(@PreviewParameter(AmountBlockPreviewProvider::class) value: SendStates.AmountState) { + TangemThemePreview { AmountBlock( amountState = value, isSuccess = false, @@ -98,21 +99,6 @@ private fun AmountBlockPreview_Light( } } -@Preview -@Composable -private fun AmountBlockPreview_Dark( - @PreviewParameter(AmountBlockPreviewProvider::class) value: SendStates.AmountState, -) { - TangemTheme(isDark = true) { - AmountBlock( - amountState = value, - isSuccess = true, - isEditingDisabled = false, - onClick = {}, - ) - } -} - private class AmountBlockPreviewProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt index 54ef73296a..9e762ae382 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.ui.send +import android.content.res.Configuration import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -15,6 +16,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.rows.SelectorRowItem +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.send.impl.R @@ -110,21 +112,10 @@ private fun BoxScope.FeeError(feeSelectorState: FeeSelectorState) { // region Preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun FeeBlockPreview_Light(@PreviewParameter(FeeBlockPreviewProvider::class) value: SendStates.FeeState) { - TangemTheme { - FeeBlock( - feeState = value, - isSuccess = true, - onClick = {}, - ) - } -} - -@Preview -@Composable -private fun FeeBlockPreview_Dark(@PreviewParameter(FeeBlockPreviewProvider::class) value: SendStates.FeeState) { - TangemTheme(isDark = true) { +private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) value: SendStates.FeeState) { + TangemThemePreview { FeeBlock( feeState = value, isSuccess = true, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt index e3b92db56c..0b4752d640 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.ui.send +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -15,6 +16,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.core.ui.components.icons.identicon.IdentIcon import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fields.SendTextField @@ -97,26 +99,12 @@ private fun MemoBlock(memo: SendTextField.RecipientMemo?) { // region Preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun RecipientBlockPreview_Light( +private fun RecipientBlockPreview( @PreviewParameter(RecipientBlockPreviewProvider::class) value: SendStates.RecipientState, ) { - TangemTheme { - RecipientBlock( - recipientState = value, - isSuccess = true, - isEditingDisabled = false, - onClick = {}, - ) - } -} - -@Preview -@Composable -private fun RecipientBlockPreview_Dark( - @PreviewParameter(RecipientBlockPreviewProvider::class) value: SendStates.RecipientState, -) { - TangemTheme(isDark = true) { + TangemThemePreview { RecipientBlock( recipientState = value, isSuccess = true, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt index 35f1fb8fe4..8b1ac6f243 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt @@ -23,6 +23,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.domain.models.ui.FeeType import com.tangem.feature.swap.models.states.ChooseFeeBottomSheetConfig import com.tangem.feature.swap.models.states.FeeItemState @@ -168,7 +169,7 @@ private fun ChooseFeeBottomSheetContent_Preview() { ), ).toImmutableList() Column { - TangemTheme(isDark = true) { + TangemThemePreview(isDark = true) { ChooseFeeBottomSheetContent( ChooseFeeBottomSheetConfig( selectedFee = FeeType.NORMAL, @@ -183,7 +184,7 @@ private fun ChooseFeeBottomSheetContent_Preview() { SpacerH24() - TangemTheme(isDark = false) { + TangemThemePreview(isDark = false) { ChooseFeeBottomSheetContent( ChooseFeeBottomSheetConfig( selectedFee = FeeType.NORMAL, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt index 4015137fcd..960b9d3373 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt @@ -18,6 +18,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig import com.tangem.feature.swap.models.states.PercentDifference import com.tangem.feature.swap.models.states.ProviderState @@ -128,7 +129,7 @@ private fun ChooseProviderBottomSheet_Preview() { alertText = stringReference("Unavailable"), ), ) - TangemTheme(isDark = false) { + TangemThemePreview(isDark = false) { ChooseProviderBottomSheetContent( ChooseProviderBottomSheetConfig( selectedProviderId = "1", diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/FeeItem.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/FeeItem.kt index f379ae3511..e4c3b974ca 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/FeeItem.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/FeeItem.kt @@ -12,6 +12,7 @@ import com.tangem.core.ui.components.rows.SimpleActionRow import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.domain.models.ui.FeeType import com.tangem.feature.swap.models.states.FeeItemState @@ -63,13 +64,13 @@ private fun FeeItemPreview() { onClick = {}, ) Column { - TangemTheme(isDark = false) { + TangemThemePreview(isDark = false) { FeeItem(state = state) } SpacerH24() - TangemTheme(isDark = true) { + TangemThemePreview(isDark = true) { FeeItem(state = state) } } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt index 723d81e622..2707fa77b2 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.ui +import android.content.res.Configuration import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -27,6 +28,7 @@ import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.models.states.PercentDifference import com.tangem.feature.swap.models.states.ProviderState @@ -390,25 +392,12 @@ private fun PermissionBadgeItem(modifier: Modifier = Modifier) { // region Preview @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun ProviderItemPreview_Light( +private fun ProviderItemPreview( @PreviewParameter(ProviderItemParameterProvider::class) state: Pair, ) { - TangemTheme { - ProviderItem( - modifier = Modifier.background(TangemTheme.colors.background.action), - state = state.first, - isSelected = state.second, - ) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun ProviderItemPreview_Dark( - @PreviewParameter(ProviderItemParameterProvider::class) state: Pair, -) { - TangemTheme(isDark = true) { + TangemThemePreview { ProviderItem( modifier = Modifier.background(TangemTheme.colors.background.action), state = state.first, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt index b915b4c0a5..8ac175588a 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.ui +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -17,6 +18,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.models.ApprovePermissionButton import com.tangem.feature.swap.models.ApproveType import com.tangem.feature.swap.models.CancelPermissionButton @@ -291,17 +293,10 @@ private fun getTitleForApproveType(approveType: ApproveType): String = when (app // region preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_AgreementBottomSheet_InLightTheme() { - TangemTheme(isDark = false) { - SwapPermissionBottomSheetContent(content = previewData) - } -} - -@Preview -@Composable -private fun Preview_AgreementBottomSheet_InDarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_AgreementBottomSheet() { + TangemThemePreview { SwapPermissionBottomSheetContent(content = previewData) } } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index f0441ea811..1137f0f264 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.ui +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -30,6 +31,7 @@ import com.tangem.core.ui.extensions.getActiveIconResByCoinId import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.domain.models.ui.FeeType import com.tangem.feature.swap.domain.models.ui.PriceImpact import com.tangem.feature.swap.models.* @@ -512,9 +514,10 @@ private val state = SwapStateHolder( ) @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun SwapScreenContentPreview() { - TangemTheme(isDark = false) { + TangemThemePreview { SwapScreenContent(state = state, modifier = Modifier) } } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt index 930039d697..081b492357 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt @@ -27,6 +27,7 @@ import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.models.* import com.tangem.feature.swap.presentation.R import kotlinx.collections.immutable.ImmutableList @@ -303,7 +304,7 @@ private val title = TokenToSelectState.Title( @Preview @Composable private fun TokenScreenPreview() { - TangemTheme(isDark = false) { + TangemThemePreview { SwapSelectTokenScreen( state = SwapSelectTokenStateHolder( availableTokens = listOf(title, token, token, token).toImmutableList(), @@ -320,7 +321,7 @@ private fun TokenScreenPreview() { @Preview @Composable private fun EmptyTokensListPreview() { - TangemTheme(isDark = false) { + TangemThemePreview { EmptyTokensList() } } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt index a562a22435..11ade5db09 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.ui +import android.content.res.Configuration import androidx.annotation.StringRes import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -18,6 +19,7 @@ import com.tangem.core.ui.components.inputrow.InputRowImage import com.tangem.core.ui.components.transactions.TransactionDoneTitle import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType import com.tangem.feature.swap.models.SwapSuccessStateHolder import com.tangem.feature.swap.presentation.R @@ -167,19 +169,11 @@ private val state = SwapSuccessStateHolder( ) @Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_Success_InLightTheme() { - TangemTheme(isDark = false) { +private fun Preview_Success() { + TangemThemePreview { SwapSuccessScreen(state) {} } } - -@Preview(showBackground = true) -@Composable -private fun Preview_Success_InDarkTheme() { - TangemTheme(isDark = true) { - SwapSuccessScreen(state) {} - } -} - // endregion preview \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index bd77a3f66e..db9d18b78e 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -38,6 +38,7 @@ import coil.request.ImageRequest import com.tangem.core.ui.R import com.tangem.core.ui.components.* import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.ImageBackgroundContrastChecker import com.tangem.feature.swap.domain.models.ui.PriceImpact import com.tangem.feature.swap.models.TransactionCardType @@ -498,7 +499,7 @@ private fun makePriceImpactBalanceWarning(value: String, priceImpactPercents: In @Preview(widthDp = 328, heightDp = 116, showBackground = true) @Composable private fun Preview_TransactionCard_InLightTheme() { - TangemTheme(isDark = false) { + TangemThemePreview(isDark = false) { TransactionCardPreview() } } @@ -506,7 +507,7 @@ private fun Preview_TransactionCard_InLightTheme() { @Preview(widthDp = 328, heightDp = 116, showBackground = true) @Composable private fun Preview_TransactionCardWithPriceImpact_InLightTheme() { - TangemTheme(isDark = false) { + TangemThemePreview(isDark = false) { TransactionCardPreviewWithPriceImpact() } } @@ -514,7 +515,7 @@ private fun Preview_TransactionCardWithPriceImpact_InLightTheme() { @Preview(widthDp = 328, heightDp = 116, showBackground = true) @Composable private fun Preview_TransactionCardWithoutPriceImpact_InLightTheme() { - TangemTheme(isDark = false) { + TangemThemePreview(isDark = false) { TransactionCardPreviewWithoutPriceImpact() } } @@ -522,7 +523,7 @@ private fun Preview_TransactionCardWithoutPriceImpact_InLightTheme() { @Preview(widthDp = 328, heightDp = 116, showBackground = true) @Composable private fun Preview_TransactionCard_InDarkTheme() { - TangemTheme(isDark = false) { + TangemThemePreview(isDark = false) { TransactionCardPreview() } } @@ -530,7 +531,7 @@ private fun Preview_TransactionCard_InDarkTheme() { @Preview(widthDp = 328, heightDp = 116, showBackground = true) @Composable private fun Preview_TransactionCardWithPriceImpact_InDarkTheme() { - TangemTheme(isDark = false) { + TangemThemePreview(isDark = false) { TransactionCardPreviewWithPriceImpact() } } @@ -538,7 +539,7 @@ private fun Preview_TransactionCardWithPriceImpact_InDarkTheme() { @Preview(widthDp = 328, heightDp = 116, showBackground = true) @Composable private fun Preview_TransactionCardWithoutPriceImpact_InDarkTheme() { - TangemTheme(isDark = false) { + TangemThemePreview(isDark = false) { TransactionCardPreviewWithoutPriceImpact() } } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/WebViewBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/WebViewBottomSheet.kt index eb2df9fe19..00a27d8a22 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/WebViewBottomSheet.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/WebViewBottomSheet.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.ui +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight @@ -14,6 +15,7 @@ import com.google.accompanist.web.rememberWebViewState import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.models.states.WebViewBottomSheetConfig @Composable @@ -55,21 +57,10 @@ private fun WebViewBottomSheetContent(content: WebViewBottomSheetConfig) { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_WebViewBottomSheetContent_InLightTheme() { - TangemTheme(isDark = false) { - WebViewBottomSheetContent( - content = WebViewBottomSheetConfig( - url = "https://tangem.com/en/", - ), - ) - } -} - -@Preview -@Composable -private fun Preview_WebViewBottomSheetContent_InDarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_WebViewBottomSheetContent() { + TangemThemePreview { WebViewBottomSheetContent( content = WebViewBottomSheetConfig( url = "https://tangem.com/en/", diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt index f8d9df0a78..830d10591c 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tester.presentation.actions +import android.content.res.Configuration import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -12,6 +13,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.feature.tester.impl.R @@ -85,17 +87,10 @@ private fun TesterActionsScreenSample(modifier: Modifier = Modifier) { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun TesterActionsScreenPreview_Light() { - TangemTheme { - TesterActionsScreenSample() - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun TesterActionsScreenPreview_Dark() { - TangemTheme(isDark = true) { +private fun TesterActionsScreenPreview() { + TangemThemePreview { TesterActionsScreenSample() } } diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt index 7ce0c9ff6d..74dddd26f0 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tester.presentation.featuretoggles.ui +import android.content.res.Configuration import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -20,6 +21,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.tester.impl.R import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatureToggle import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureTogglesContentState @@ -81,26 +83,10 @@ private fun FeatureToggleItem(toggle: TesterFeatureToggle, onCheckedChange: (Boo } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun PreviewFeatureTogglesScreen_InLightTheme() { - TangemTheme(isDark = false) { - FeatureTogglesScreen( - state = FeatureTogglesContentState( - featureToggles = listOf( - TesterFeatureToggle(name = "FEATURE_TOGGLE_1", isEnabled = true), - TesterFeatureToggle(name = "FEATURE_TOGGLE_2", isEnabled = false), - ), - onToggleValueChange = { _, _ -> }, - onBackClick = {}, - ), - ) - } -} - -@Preview -@Composable -private fun PreviewFeatureTogglesScreen_InDarkTheme() { - TangemTheme(isDark = true) { +private fun PreviewFeatureTogglesScreen() { + TangemThemePreview { FeatureTogglesScreen( state = FeatureTogglesContentState( featureToggles = listOf( diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/ui/TesterMenuScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/ui/TesterMenuScreen.kt index 4e5d7f056f..39d9c5b95c 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/ui/TesterMenuScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/ui/TesterMenuScreen.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tester.presentation.menu.ui +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -15,6 +16,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.tester.impl.R import com.tangem.feature.tester.presentation.menu.state.TesterMenuContentState @@ -64,23 +66,10 @@ internal fun TesterMenuScreen(state: TesterMenuContentState) { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun PreviewTesterMenuScreen_InLightTheme() { - TangemTheme(isDark = false) { - TesterMenuScreen( - state = TesterMenuContentState( - onBackClick = {}, - onFeatureTogglesClick = {}, - onTesterActionsClick = {}, - ), - ) - } -} - -@Preview -@Composable -private fun PreviewTesterMenuScreen_InDarkTheme() { - TangemTheme(isDark = true) { +private fun PreviewTesterMenuScreen() { + TangemThemePreview { TesterMenuScreen( state = TesterMenuContentState( onBackClick = {}, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index 070d2b0065..ebce26c35c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui +import android.content.res.Configuration import androidx.activity.compose.BackHandler import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box @@ -39,6 +40,7 @@ import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification @@ -184,21 +186,12 @@ internal fun TokenDetailsEventEffect(snackbarHostState: SnackbarHostState, event // region Preview @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun TokenDetailsScreenPreview_Light( +private fun TokenDetailsScreenPreview( @PreviewParameter(TokenDetailsScreenParameterProvider::class) state: TokenDetailsState, ) { - TangemTheme { - TokenDetailsScreen(state) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun TokenDetailsScreenPreview_Dark( - @PreviewParameter(TokenDetailsScreenParameterProvider::class) state: TokenDetailsState, -) { - TangemTheme(isDark = true) { + TangemThemePreview { TokenDetailsScreen(state) } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt index e8fd329b15..3f448ae33a 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components +import android.content.res.Configuration import androidx.compose.foundation.layout.* import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -14,6 +15,7 @@ import com.tangem.common.Strings.STARS import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.buttons.HorizontalActionChips import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState @@ -129,21 +131,12 @@ private fun CryptoBalance( } @Preview(widthDp = 328, heightDp = 152) +@Preview(widthDp = 328, heightDp = 152, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_TokenDetailsBalanceBlock_LightTheme( +private fun Preview_TokenDetailsBalanceBlock( @PreviewParameter(TokenDetailsBalanceBlockStateProvider::class) state: TokenDetailsBalanceBlockState, ) { - TangemTheme(isDark = false) { - TokenDetailsBalanceBlock(state = state, isBalanceHidden = false) - } -} - -@Preview(widthDp = 328, heightDp = 152) -@Composable -private fun Preview_TokenDetailsBalanceBlock_DarkTheme( - @PreviewParameter(TokenDetailsBalanceBlockStateProvider::class) state: TokenDetailsBalanceBlockState, -) { - TangemTheme(isDark = true) { + TangemThemePreview { TokenDetailsBalanceBlock(state = state, isBalanceHidden = false) } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsTopAppBar.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsTopAppBar.kt index 1ed6603ba3..3ea60b1140 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsTopAppBar.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsTopAppBar.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components +import android.content.res.Configuration import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -18,6 +19,7 @@ import androidx.compose.ui.util.fastForEach import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsAppBarMenuConfig import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarConfig @@ -96,9 +98,10 @@ private fun AppBarDropdownItem( } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_TokenDetailsAppBarDropdownItem_LightTheme() { - TangemTheme(isDark = false) { +private fun Preview_TokenDetailsAppBarDropdownItem() { + TangemThemePreview { AppBarDropdownItem( modifier = Modifier.background(TangemTheme.colors.background.primary), dismissParent = {}, @@ -112,33 +115,10 @@ private fun Preview_TokenDetailsAppBarDropdownItem_LightTheme() { } @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_TokenDetailsAppBarDropdownItem_DarkTheme() { - TangemTheme(isDark = true) { - AppBarDropdownItem( - modifier = Modifier.background(TangemTheme.colors.background.primary), - dismissParent = {}, - item = TokenDetailsAppBarMenuConfig.MenuItem( - title = TextReference.Res(id = R.string.token_details_hide_token), - textColorProvider = { TangemTheme.colors.text.warning }, - onClick = { }, - ), - ) - } -} - -@Preview -@Composable -private fun Preview_TokenDetailsTopAppBar_LightTheme() { - TangemTheme(isDark = false) { - TokenDetailsTopAppBar(config = TokenDetailsPreviewData.tokenDetailsTopAppBarConfig) - } -} - -@Preview -@Composable -private fun Preview_TokenDetailsTopAppBar_DarkTheme() { - TangemTheme(isDark = true) { +private fun Preview_TokenDetailsTopAppBar() { + TangemThemePreview { TokenDetailsTopAppBar(config = TokenDetailsPreviewData.tokenDetailsTopAppBarConfig) } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt index 3982a8dc21..37f036d60f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.Text @@ -18,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenInfoBlockState import com.tangem.features.tokendetails.impl.R @@ -136,23 +138,13 @@ private val GrayscaleColorFilter: ColorFilter get() = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(GRAY_SCALE_SATURATION) }) @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun Preview_TokenInfoBlock_LightTheme( +private fun Preview_TokenInfoBlock( @PreviewParameter(TokenInfoStateProvider::class) state: TokenInfoBlockState, ) { - TangemTheme(isDark = false) { - TokenInfoBlock(state, Modifier.background(TangemTheme.colors.background.secondary)) - } -} - -@Preview -@Composable -private fun Preview_TokenInfoBlock_DarkTheme( - @PreviewParameter(TokenInfoStateProvider::class) - state: TokenInfoBlockState, -) { - TangemTheme(isDark = true) { + TangemThemePreview { TokenInfoBlock(state, Modifier.background(TangemTheme.colors.background.secondary)) } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeProvider.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeProvider.kt index 5f3527a8eb..7d1c2f14b5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeProvider.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeProvider.kt @@ -22,6 +22,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.inputrow.InputRowBestRate import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview @Composable internal fun ExchangeProvider( @@ -90,7 +91,7 @@ internal fun ExchangeProvider( @Preview(showBackground = true) @Composable private fun ExchangeProvider_Preview() { - TangemTheme(isDark = false) { + TangemThemePreview(isDark = false) { ExchangeProvider( providerName = TextReference.Str("Changelly"), providerType = TextReference.Str("CEX"), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt index 53e398e71f..1945621d19 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange +import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -25,6 +26,7 @@ import com.tangem.core.ui.components.atoms.text.TextEllipsis import com.tangem.core.ui.components.currency.tokenicon.TokenIcon import com.tangem.core.ui.components.currency.tokenicon.TokenIconState import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.domain.models.domain.ExchangeStatus import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTransactionsState import com.tangem.features.tokendetails.impl.R @@ -195,31 +197,12 @@ private fun ExchangeStatusItem( //region Preview @Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun ExchangeStatusItemPreview_Light( +private fun ExchangeStatusItemPreview( @PreviewParameter(ExchangeStatusItemsPreviewParameterProvider::class) amount: String, ) { - TangemTheme { - ExchangeStatusItem( - providerName = "ChangeNow", - fromTokenIconState = TokenIconState.Loading, - toTokenIconState = TokenIconState.Loading, - fromAmount = amount, - fromSymbol = "USDT", - toSymbol = "USDT", - onClick = {}, - infoIconRes = null, - infoIconTint = null, - ) - } -} - -@Preview -@Composable -private fun ExchangeStatusItemPreview_Dark( - @PreviewParameter(ExchangeStatusItemsPreviewParameterProvider::class) amount: String, -) { - TangemTheme(isDark = true) { + TangemThemePreview { ExchangeStatusItem( providerName = "ChangeNow", fromTokenIconState = TokenIconState.Loading, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt index c5af5a2cf6..c0d0a73b1a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.common.component +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.Icon @@ -13,6 +14,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.wallet.impl.R import org.burnoutcrew.reorderable.ReorderableLazyListState import org.burnoutcrew.reorderable.detectReorder @@ -92,17 +94,10 @@ private fun NetworkGroupItemSample(isDraggable: Boolean) { } @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider::class) isDraggable: Boolean) { - TangemTheme { - NetworkGroupItemSample(isDraggable) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun NetworkGroupItemPreview_Dark(@PreviewParameter(NetworkGroupProvider::class) isDraggable: Boolean) { - TangemTheme(isDark = true) { +private fun NetworkGroupItemPreview(@PreviewParameter(NetworkGroupProvider::class) isDraggable: Boolean) { + TangemThemePreview { NetworkGroupItemSample(isDraggable) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt index 0b20c2a43a..989965b112 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt @@ -20,6 +20,7 @@ import com.tangem.core.ui.components.currency.tokenicon.TokenIcon import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.extensions.rememberHapticFeedback import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.common.component.token.* import com.tangem.feature.wallet.presentation.common.state.TokenItemState @@ -392,7 +393,7 @@ private fun calculateLayoutHeight( @Preview(widthDp = 360) @Composable private fun Preview_TokenItem_InLight(@PreviewParameter(TokenItemStateProvider::class) state: TokenItemState) { - TangemTheme(isDark = false) { + TangemThemePreview(isDark = false) { TokenItem(state = state, isBalanceHidden = false) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt index 294fe204cd..4efcf827cb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.organizetokens +import android.content.res.Configuration import androidx.activity.compose.BackHandler import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background @@ -34,6 +35,7 @@ import com.tangem.core.ui.components.buttons.actions.RoundedActionButton import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.common.component.DraggableNetworkGroupItem @@ -373,21 +375,12 @@ private fun getItemShape(roundingMode: DraggableItem.RoundingMode, radius: Dp): // region Preview @Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -private fun OrganizeTokensScreenPreview_Light( +private fun OrganizeTokensScreenPreview( @PreviewParameter(OrganizeTokensStateProvider::class) state: OrganizeTokensState, ) { - TangemTheme { - OrganizeTokensScreen(state) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Composable -private fun OrganizeTokensScreenPreview_Dark( - @PreviewParameter(OrganizeTokensStateProvider::class) state: OrganizeTokensState, -) { - TangemTheme(isDark = true) { + TangemThemePreview { OrganizeTokensScreen(state) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/TokenActionsBottomSheet.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/TokenActionsBottomSheet.kt index 865a52268f..5e4db0ab08 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/TokenActionsBottomSheet.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/TokenActionsBottomSheet.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable @@ -14,6 +15,7 @@ import com.tangem.core.ui.components.getDefaultRowColors import com.tangem.core.ui.components.getWarningRowColors import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.wallet.state.model.ActionsBottomSheetConfig import com.tangem.feature.wallet.presentation.wallet.state.model.TokenActionButtonConfig @@ -49,24 +51,13 @@ private fun ActionsBottomSheetContent(actions: ImmutableList Date: Fri, 3 May 2024 13:33:46 +0500 Subject: [PATCH 52/98] Updated on 2026-08-14 --- .../state/factory/TokenDetailsNotificationConverter.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index d88f65365c..048afdb96e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -2,6 +2,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState @@ -65,7 +66,11 @@ internal class TokenDetailsNotificationConverter( CryptoCurrencyWarning.SomeNetworksUnreachable -> NetworksUnreachable is CryptoCurrencyWarning.SomeNetworksNoAccount -> NetworksNoAccount( network = warning.amountCurrency.name, - amount = warning.amountToCreateAccount.toString(), + amount = BigDecimalFormatter.formatCryptoAmount( + cryptoAmount = warning.amountToCreateAccount, + cryptoCurrency = "", + decimals = warning.amountCurrency.decimals, + ), symbol = warning.amountCurrency.symbol, ) is CryptoCurrencyWarning.TopUpWithoutReserve -> TopUpWithoutReserve From 3a95fbcfb50cfae7a2ce166f85d3898fe560fc14 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 6 May 2024 12:49:48 +0100 Subject: [PATCH 53/98] Updated on 2026-08-14 --- .../core/ui/extensions/BlockchainIcons.kt | 5 +++++ .../src/main/res/drawable/ic_joystream_22.xml | 14 ++++++++++++++ .../src/main/res/drawable/img_joystream_22.xml | 17 +++++++++++++++++ gradle/dependencies.toml | 2 +- .../tangem/blockchainsdk/utils/Blockchain.kt | 3 +++ 5 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 core/ui/src/main/res/drawable/ic_joystream_22.xml create mode 100644 core/ui/src/main/res/drawable/img_joystream_22.xml diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index 1a95df0142..e6e331c589 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -67,6 +67,7 @@ fun getActiveIconRes(blockchainId: String): Int { "taraxa", "taraxa/test" -> R.drawable.img_taraxa_22 "radiant" -> R.drawable.img_radiant_22 "base" -> R.drawable.img_base_22 + "joystream" -> R.drawable.img_joystream_22 else -> R.drawable.ic_alert_24 } } @@ -135,6 +136,7 @@ fun getActiveIconResByNetworkId(networkId: String): Int { "taraxa", "taraxa/test" -> R.drawable.img_taraxa_22 "radiant" -> R.drawable.img_radiant_22 "base" -> R.drawable.img_base_22 + "joystream" -> R.drawable.img_joystream_22 else -> R.drawable.ic_alert_24 } } @@ -200,6 +202,7 @@ fun getActiveIconResByCoinId(coinId: String): Int { "taraxa" -> R.drawable.img_taraxa_22 "radiant" -> R.drawable.img_radiant_22 "base" -> R.drawable.img_base_22 + "joystream" -> R.drawable.img_joystream_22 else -> R.drawable.ic_alert_24 } } @@ -268,6 +271,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int { "taraxa", "taraxa/test" -> R.drawable.ic_taraxa_22 "radiant" -> R.drawable.ic_radiant_22 "base", "base/test" -> R.drawable.ic_base_22 + "joystream" -> R.drawable.ic_joystream_22 else -> R.drawable.ic_alert_24 } } @@ -336,6 +340,7 @@ fun getGreyedOutIconResByNetworkId(networkId: String): Int { "taraxa", "taraxa/test" -> R.drawable.ic_taraxa_22 "radiant" -> R.drawable.ic_radiant_22 "base", "base/test" -> R.drawable.ic_base_22 + "joystream" -> R.drawable.ic_joystream_22 else -> R.drawable.ic_alert_24 } } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_joystream_22.xml b/core/ui/src/main/res/drawable/ic_joystream_22.xml new file mode 100644 index 0000000000..91f187f81c --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_joystream_22.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/core/ui/src/main/res/drawable/img_joystream_22.xml b/core/ui/src/main/res/drawable/img_joystream_22.xml new file mode 100644 index 0000000000..2168e5839e --- /dev/null +++ b/core/ui/src/main/res/drawable/img_joystream_22.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 99fc8ec14e..6e2f105776 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -86,7 +86,7 @@ room = "2.6.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-620" +tangemBlockchainSdk = "develop-621" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-351" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index 1ae43f5950..e8ac19e194 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -116,6 +116,7 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "base/test" -> Blockchain.BaseTestnet "koinos" -> Blockchain.Koinos "koinos/test" -> Blockchain.KoinosTestnet + "joystream" -> Blockchain.Joystream else -> null } } @@ -233,6 +234,7 @@ fun Blockchain.toNetworkId(): String { Blockchain.BaseTestnet -> "base/test" Blockchain.Koinos -> "koinos" Blockchain.KoinosTestnet -> "koinos/test" + Blockchain.Joystream -> "joystream" } } @@ -307,6 +309,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Taraxa, Blockchain.TaraxaTestnet -> "taraxa" Blockchain.Base, Blockchain.BaseTestnet -> "base-ethereum" Blockchain.Koinos, Blockchain.KoinosTestnet -> "koinos" + Blockchain.Joystream -> "joystream" } } From 56dddbad433daeb3d84cc841e655841b404e6414 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 6 May 2024 15:29:34 +0200 Subject: [PATCH 54/98] Updated on 2026-08-14 --- .../tokens/utils/ResponseCryptoCurrenciesFactory.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/ResponseCryptoCurrenciesFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/ResponseCryptoCurrenciesFactory.kt index aa21c01410..fd7493005d 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/ResponseCryptoCurrenciesFactory.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/ResponseCryptoCurrenciesFactory.kt @@ -92,6 +92,17 @@ class ResponseCryptoCurrenciesFactory { // [REDACTED_JIRA] Blockchain.Dischain, Blockchain.Arbitrum, + Blockchain.ArbitrumTestnet, + Blockchain.Aurora, + Blockchain.AuroraTestnet, + Blockchain.Manta, + Blockchain.MantaTestnet, + Blockchain.ZkSyncEra, + Blockchain.ZkSyncEraTestnet, + Blockchain.PolygonZkEVM, + Blockchain.PolygonZkEVMTestnet, + Blockchain.Base, + Blockchain.BaseTestnet, -> this.fullName else -> responseToken.name } From ce9cfb001d25dd1ccd87a625d074befc4790a972 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 6 May 2024 16:37:44 +0200 Subject: [PATCH 55/98] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 9 ++++----- core/res/src/main/res/values/strings.xml | 11 +++++------ .../tokens/model/warnings/CryptoCurrencyWarning.kt | 4 +--- .../domain/tokens/GetCryptoCurrencyActionsUseCase.kt | 2 +- .../domain/tokens/GetCurrencyWarningsUseCase.kt | 3 --- .../tokens/model/ScenarioUnavailabilityReason.kt | 2 +- .../TokenDetailsNotificationsAnalyticsSender.kt | 1 - .../state/components/TokenDetailsNotification.kt | 12 ++---------- .../factory/TokenDetailsNotificationConverter.kt | 3 --- .../state/factory/TokenDetailsStateFactory.kt | 6 +++--- .../intents/WalletCurrencyActionsClickIntents.kt | 6 +++--- 11 files changed, 20 insertions(+), 39 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 6e9e7bf51e..b8a5d12fe9 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -58,9 +58,9 @@ Тип безопасности Настройки карты Ввиду особенностей сети Cardano при транзакции токена %1$s помимо комиссии сети будет списано %2$s - Для совершения транзакции %1$s, вам необходимо внести немного %2$s (%3$s), чтобы покрыть комиссию сети и минимальное значение ADA для отправки. + Чтобы совершить транзакцию %1$s, вы должны внести некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA. (рекомендуется баланс в размере 5 ADA) Недостаточно ADA для отправки токена - Вывод всего баланса ADA невозможен при наличии средств на токенах сети Cardano. Сначала выведите средства на ваших токенах. + Сеть Cardano не позволяет вам оставлять на своем балансе сумму ниже минимального значения %1$s, если у вас есть токены. Пожалуйста, убедитесь, что у вас осталось немного ADA. Принять Доступ запрещен Применить @@ -557,7 +557,8 @@ У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. - Продажа средств станет доступной после завершения транзакции %s + Продажа средств станет доступной после завершения транзакции(-ий) в сети %s + Отправка средств станет доступной после завершения транзакции(-ий) в сети %s В данный момент продажа %s недоступна. Следите за нашими обновлениями. Выберите адрес Сгенерировать XPUB @@ -697,8 +698,6 @@ Необходима плата за аренду сети %1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети. Недостаточно %1$s для оплаты комиссии сети - Отправка средств станет доступной после завершения транзакции(-ий) в сети %s - Отправка средств станет доступной после завершения транзакции %s Транзакция в обработке Сеть Солана испытывает высокую нагрузку. Если Ваша транзакция не прошла в течение 2 минут, повторите её отправку. Оповещение сети Солана diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 2fb4ea6a93..ef843270c8 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -56,10 +56,10 @@ Reset to Factory Settings Security Mode Card Settings - Due to the peculiarities of the Cardano network, when transacting the %1$s token, in addition to the network commission, %2$s will be charged - To make a %1$s transaction, you must deposit some %2$s (%3$s) to cover the network fee and minimum ADA value + Due to the peculiarities of the Cardano network, when transacting the %1$s token, in addition to the network commission, %2$s will be charged + To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value. (5 ADA balance recommended) Insufficient ADA to token transfer - Withdrawal of the entire ADA balance is not possible if funds are available in Cardano network tokens. First, withdraw funds on your tokens. + The Cardano network does not allow you to leave an amount on your balance below the minimum value %1$s if you have tokens. Please ensure that you have some ADA remaining. Accept Access denied Apply @@ -557,7 +557,8 @@ You do not have funds to sell. Top up your account to be able to sell funds from it. You do not have funds to send. Top up your account to be able to send funds from it. Swapping %s is not available at the moment. Please check our updates. - Selling funds will be available once the %s transaction is complete + Selling funds will be available once the pending transaction(s) in network %s is complete + Sending funds will be available once the pending transaction(s) in network %s is complete Selling %s is not available at the moment. Please check our updates. Choose address Generate XPUB @@ -695,8 +696,6 @@ Network rent fee required %1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. Insufficient %1$s to cover network fee - Sending funds will be available once the pending transaction(s) in network %s is complete - Sending funds will be available once the %s transaction is complete Transaction pending The Solana network is congested. If your transaction is not processed within 2 minutes, please repeat the transaction. Solana Network Alert diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt index 6e477d8a10..5c1cad5f44 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt @@ -31,7 +31,7 @@ sealed class CryptoCurrencyWarning { val amountCurrency: CryptoCurrency, ) : CryptoCurrencyWarning() - object TopUpWithoutReserve : CryptoCurrencyWarning() + data object TopUpWithoutReserve : CryptoCurrencyWarning() /** * Represents wallet blockchain rent @@ -41,8 +41,6 @@ sealed class CryptoCurrencyWarning { */ data class Rent(val rent: BigDecimal, val exemptionAmount: BigDecimal) : CryptoCurrencyWarning() - data class HasPendingTransactions(val blockchainSymbol: String) : CryptoCurrencyWarning() - data class SwapPromo( val startDateTime: DateTime, val endDateTime: DateTime, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index 74e89db52c..d0155c6c62 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -232,7 +232,7 @@ class GetCryptoCurrencyActionsUseCase( ) -> { ScenarioUnavailabilityReason.PendingTransaction( withdrawalScenario = ScenarioUnavailabilityReason.WithdrawalScenario.SEND, - cryptoCurrencySymbol = coinStatus?.currency?.symbol.orEmpty(), + networkName = coinStatus?.currency?.network?.name.orEmpty(), ) } else -> { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index 91430442e1..43a7a2fda8 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -167,9 +167,6 @@ class GetCurrencyWarningsUseCase( when { tokenStatus != null && coinStatus != null -> { buildList { - if (currenciesRepository.hasPendingTransactions(tokenStatus, coinStatus)) { - add(CryptoCurrencyWarning.HasPendingTransactions(coinStatus.currency.symbol)) - } getFeeWarning( userWalletId = userWalletId, coinStatus = coinStatus, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt index 27315e15a7..e241178026 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt @@ -6,7 +6,7 @@ sealed class ScenarioUnavailabilityReason { // send&sell-specific data class PendingTransaction( val withdrawalScenario: WithdrawalScenario, - val cryptoCurrencySymbol: String, + val networkName: String, ) : ScenarioUnavailabilityReason() data class EmptyBalance(val withdrawalScenario: WithdrawalScenario) : ScenarioUnavailabilityReason() diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt index 3a470f1a30..9236e01d5e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt @@ -37,7 +37,6 @@ internal class TokenDetailsNotificationsAnalyticsSender( ) is TokenDetailsNotification.NetworksUnreachable, is TokenDetailsNotification.ExistentialDeposit, - is TokenDetailsNotification.HasPendingTransactions, is TokenDetailsNotification.NetworksNoAccount, is TokenDetailsNotification.TopUpWithoutReserve, is TokenDetailsNotification.RentInfo, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index a33cc576e3..bed8a2a0e2 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -66,7 +66,7 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { ), ) - object NetworksUnreachable : Warning( + data object NetworksUnreachable : Warning( title = resourceReference(R.string.warning_network_unreachable_title), subtitle = resourceReference(R.string.warning_network_unreachable_message), ) @@ -163,16 +163,8 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { ), ) - object TopUpWithoutReserve : Informational( + data object TopUpWithoutReserve : Informational( title = resourceReference(id = R.string.warning_no_account_title), subtitle = resourceReference(id = R.string.no_account_send_to_create), ) - - class HasPendingTransactions(val coinSymbol: String) : Informational( - title = resourceReference(R.string.warning_send_blocked_pending_transactions_title), - subtitle = resourceReference( - id = R.string.warning_send_blocked_pending_transactions_message, - formatArgs = wrappedList(coinSymbol), - ), - ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index 048afdb96e..01535ea1b4 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -74,9 +74,6 @@ internal class TokenDetailsNotificationConverter( symbol = warning.amountCurrency.symbol, ) is CryptoCurrencyWarning.TopUpWithoutReserve -> TopUpWithoutReserve - is CryptoCurrencyWarning.HasPendingTransactions -> HasPendingTransactions( - coinSymbol = warning.blockchainSymbol, - ) is CryptoCurrencyWarning.SwapPromo -> SwapPromo( startDateTime = warning.startDateTime, endDateTime = warning.endDateTime, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 0109f66fa5..82d586ce66 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -337,12 +337,12 @@ internal class TokenDetailsStateFactory( is ScenarioUnavailabilityReason.PendingTransaction -> { when (unavailabilityReason.withdrawalScenario) { ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference( - id = R.string.warning_send_blocked_pending_transactions_message, - formatArgs = wrappedList(unavailabilityReason.cryptoCurrencySymbol), + id = R.string.token_button_unavailability_reason_pending_transaction_send, + formatArgs = wrappedList(unavailabilityReason.networkName), ) ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference( id = R.string.token_button_unavailability_reason_pending_transaction_sell, - formatArgs = wrappedList(unavailabilityReason.cryptoCurrencySymbol), + formatArgs = wrappedList(unavailabilityReason.networkName), ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 3294411975..d45b21bf9c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -449,12 +449,12 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( is ScenarioUnavailabilityReason.PendingTransaction -> { when (unavailabilityReason.withdrawalScenario) { ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference( - id = R.string.warning_send_blocked_pending_transactions_message, - formatArgs = wrappedList(unavailabilityReason.cryptoCurrencySymbol), + id = R.string.token_button_unavailability_reason_pending_transaction_send, + formatArgs = wrappedList(unavailabilityReason.networkName), ) ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference( id = R.string.token_button_unavailability_reason_pending_transaction_sell, - formatArgs = wrappedList(unavailabilityReason.cryptoCurrencySymbol), + formatArgs = wrappedList(unavailabilityReason.networkName), ) } } From 43b055feb97ab24a226505ed34689b4ee759dd89 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 7 May 2024 11:01:09 +0500 Subject: [PATCH 56/98] Updated on 2026-08-14 --- .../DefaultTransactionRepository.kt | 26 +++++- .../transaction/TransactionRepository.kt | 2 + .../usecase/CreateTransactionUseCase.kt | 4 + .../domain/models/ui/SwapTransactionState.kt | 2 + .../feature/swap/domain/SwapInteractorImpl.kt | 82 ++++++++++++------- .../swap/domain/di/SwapDomainModule.kt | 13 +++ .../tangem/feature/swap/ui/StateBuilder.kt | 12 +++ .../feature/swap/viewmodels/SwapViewModel.kt | 6 ++ 8 files changed, 116 insertions(+), 31 deletions(-) diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index 3fe078eaad..934a88f5c9 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -17,6 +17,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext +import java.math.BigDecimal internal class DefaultTransactionRepository( private val walletManagersFacade: WalletManagersFacade, @@ -30,6 +31,8 @@ internal class DefaultTransactionRepository( destination: String, userWalletId: UserWalletId, network: Network, + isSwap: Boolean, + hash: String?, ): TransactionData? = withContext(coroutineDispatcherProvider.io) { val blockchain = Blockchain.fromId(network.id.value) val walletManager = walletManagersFacade.getOrCreateWalletManager( @@ -38,7 +41,13 @@ internal class DefaultTransactionRepository( derivationPath = network.derivationPath.value, ) - return@withContext walletManager?.createTransaction(amount, fee, destination)?.copy( + val txAmount = if (isSwap) { + createAmountForSwap(amount) + } else { + amount + } + return@withContext walletManager?.createTransaction(txAmount, fee, destination)?.copy( + hash = hash, extras = getMemoExtras(network.id.value, memo), ) } @@ -81,4 +90,19 @@ internal class DefaultTransactionRepository( else -> null } } + + private fun createAmountForSwap(amount: Amount): Amount { + return when (amount.type) { + is AmountType.Coin -> amount + else -> { + // 1. when creates swap amount for NonNativeToken, amount should be ZERO + // 2. Amount has .Coin type, as workaround to use destinationAddress in bsdk, not contractAddress + Amount( + currencySymbol = amount.currencySymbol, + value = BigDecimal.ZERO, + decimals = amount.decimals, + ) + } + } + } } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt index 70c8b7d0ae..c28e9dbea7 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt @@ -18,6 +18,8 @@ interface TransactionRepository { destination: String, userWalletId: UserWalletId, network: Network, + isSwap: Boolean, + hash: String?, ): TransactionData? suspend fun sendTransaction( diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt index 55c5e0cd88..9fea02c12f 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt @@ -22,6 +22,8 @@ class CreateTransactionUseCase( destination: String, userWalletId: UserWalletId, network: Network, + isSwap: Boolean = false, + hash: String? = null, ) = Either.catch { requireNotNull( transactionRepository.createTransaction( @@ -31,6 +33,8 @@ class CreateTransactionUseCase( destination = destination, userWalletId = userWalletId, network = network, + isSwap = isSwap, + hash = hash, ), ) { "Failed to create transaction" } } diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapTransactionState.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapTransactionState.kt index 6ba68ddd31..bd29c42170 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapTransactionState.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapTransactionState.kt @@ -26,4 +26,6 @@ sealed class SwapTransactionState { data object UnknownError : SwapTransactionState() data class ExpressError(val dataError: DataError) : SwapTransactionState() + + data object DemoMode : SwapTransactionState() } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index bea16b1e5c..8deb74e13e 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -11,6 +11,7 @@ import com.tangem.blockchainsdk.utils.minimalAmount import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.appcurrency.repository.AppCurrencyRepository +import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -21,6 +22,7 @@ import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.transaction.error.SendTransactionError +import com.tangem.domain.transaction.usecase.CreateTransactionUseCase import com.tangem.domain.transaction.usecase.EstimateFeeUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase import com.tangem.domain.walletmanager.WalletManagersFacade @@ -36,7 +38,10 @@ import com.tangem.feature.swap.domain.models.toStringWithRightOffset import com.tangem.feature.swap.domain.models.ui.* import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager -import com.tangem.lib.crypto.models.* +import com.tangem.lib.crypto.models.AnalyticsData +import com.tangem.lib.crypto.models.ApproveTxData +import com.tangem.lib.crypto.models.ProxyAmount +import com.tangem.lib.crypto.models.ProxyFees import com.tangem.lib.crypto.models.transactions.SendTxResult import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.isNullOrZero @@ -59,6 +64,7 @@ internal class SwapInteractorImpl @Inject constructor( private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase, private val walletManagersFacade: WalletManagersFacade, private val sendTransactionUseCase: SendTransactionUseCase, + private val createTransactionUseCase: CreateTransactionUseCase, private val quotesRepository: QuotesRepository, private val dispatcher: CoroutineDispatcherProvider, private val swapTransactionRepository: SwapTransactionRepository, @@ -66,6 +72,7 @@ internal class SwapInteractorImpl @Inject constructor( private val appCurrencyRepository: AppCurrencyRepository, private val currenciesRepository: CurrenciesRepository, private val initialToCurrencyResolver: InitialToCurrencyResolver, + private val demoConfig: DemoConfig, ) : SwapInteractor { private val estimateFeeUseCase by lazy(LazyThreadSafetyMode.NONE) { @@ -473,6 +480,7 @@ internal class SwapInteractorImpl @Inject constructor( currencyToGet = currencyToGet.currency, amountToSwap = amountToSwap, fee = fee, + userWalletId = requireNotNull(getSelectedWallet()).walletId, ) } } @@ -523,29 +531,35 @@ internal class SwapInteractorImpl @Inject constructor( currencyToGet: CryptoCurrency, amountToSwap: String, fee: TxFee, + userWalletId: UserWalletId, ): SwapTransactionState { val amountDecimal = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format" } val amount = SwapAmount(amountDecimal, currencyToSend.decimals) val derivationPath = currencyToSend.network.derivationPath.value - val result = transactionManager.sendTransaction( - txData = SwapTxData( - networkId = networkId, - amountToSend = amountDecimal, - currencyToSend = swapCurrencyConverter.convert(currencyToSend), - feeAmount = fee.feeValue, - gasLimit = fee.gasLimit, - destinationAddress = swapData.transaction.txTo, - dataToSign = (swapData.transaction as ExpressTransactionModel.DEX).txData, + val txData = createTransactionUseCase( + amount = amount.value.convertToAmount(currencyToSend), + fee = getFeeForTransaction( + fee = fee, + blockchain = Blockchain.fromId(currencyToSend.network.id.value), ), + memo = null, + destination = swapData.transaction.txTo, + userWalletId = userWalletId, + network = currencyToSend.network, + hash = (swapData.transaction as ExpressTransactionModel.DEX).txData, isSwap = true, - derivationPath = derivationPath, - analyticsData = AnalyticsData( - feeType = fee.feeType.getNameForAnalytics(), - tokenSymbol = currencyToSend.symbol, - ), + ).getOrElse { + Timber.e(it) + return SwapTransactionState.UnknownError + } + + val result = sendTransactionUseCase( + txData = txData, + userWallet = requireNotNull(getSelectedWallet()), + network = currencyToSend.network, ) - return when (result) { - is SendTxResult.Success -> { + return result.fold( + ifRight = { storeLastCryptoCurrencyId(currencyToGet) SwapTransactionState.TxSent( fromAmount = amountFormatter.formatSwapAmountToUI( @@ -561,13 +575,18 @@ internal class SwapInteractorImpl @Inject constructor( txHash = userWalletManager.getLastTransactionHash(networkId, derivationPath).orEmpty(), timestamp = System.currentTimeMillis(), ) - } - SendTxResult.UserCancelledError -> SwapTransactionState.UserCancelled - is SendTxResult.BlockchainSdkError -> SwapTransactionState.BlockchainError - is SendTxResult.TangemSdkError -> SwapTransactionState.TangemSdkError - is SendTxResult.NetworkError -> SwapTransactionState.NetworkError - is SendTxResult.UnknownError -> SwapTransactionState.UnknownError - } + }, + ifLeft = { + when (it) { + SendTransactionError.UserCancelledError -> SwapTransactionState.UserCancelled + is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError + is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError + is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError + is SendTransactionError.DemoCardError -> SwapTransactionState.DemoMode + else -> SwapTransactionState.UnknownError + } + }, + ) } @Suppress("LongMethod") @@ -602,10 +621,11 @@ internal class SwapInteractorImpl @Inject constructor( currencyToSend.currency.network.backendId, exchangeDataCex.txExtraId, ) - if (txExtras == null && exchangeDataCex.txExtraId != null) { + val cardId = getSelectedWallet()?.scanResponse?.card?.cardId ?: return SwapTransactionState.UnknownError + if (txExtras == null && exchangeDataCex.txExtraId != null && !demoConfig.isDemoCardId(cardId)) { return SwapTransactionState.UnknownError } - val txData = walletManagersFacade.createTransaction( + val txData = createTransactionUseCase( amount = amount.value.convertToAmount(currencyToSend.currency), fee = getFeeForTransaction( fee = txFee, @@ -615,12 +635,13 @@ internal class SwapInteractorImpl @Inject constructor( destination = exchangeDataCex.txTo, userWalletId = userWalletId, network = currencyToSend.currency.network, - )?.copy( - extras = txExtras, - ) + ).getOrElse { + Timber.e(it) + return SwapTransactionState.UnknownError + }.copy(extras = txExtras) val result = sendTransactionUseCase( - requireNotNull(txData), + txData = txData, userWallet = requireNotNull(getSelectedWallet()), network = currencyToSend.currency.network, ) @@ -635,6 +656,7 @@ internal class SwapInteractorImpl @Inject constructor( is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError + is SendTransactionError.DemoCardError -> SwapTransactionState.DemoMode else -> SwapTransactionState.UnknownError } }, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index fc191d1d2d..9c2290da01 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -11,6 +11,7 @@ import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.transaction.TransactionRepository +import com.tangem.domain.transaction.usecase.CreateTransactionUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager @@ -40,6 +41,7 @@ class SwapDomainModule { @SwapScope getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase, @SwapScope sendTransactionUseCase: SendTransactionUseCase, + @SwapScope createTransactionUseCase: CreateTransactionUseCase, quotesRepository: QuotesRepository, swapTransactionRepository: SwapTransactionRepository, appCurrencyRepository: AppCurrencyRepository, @@ -57,6 +59,7 @@ class SwapDomainModule { getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, getMultiCryptoCurrencyStatusUseCase = getCryptoCurrencyStatusUseCase, sendTransactionUseCase = sendTransactionUseCase, + createTransactionUseCase = createTransactionUseCase, quotesRepository = quotesRepository, walletManagersFacade = walletManagersFacade, dispatcher = coroutineDispatcherProvider, @@ -65,6 +68,7 @@ class SwapDomainModule { currencyChecksRepository = currencyChecksRepository, currenciesRepository = currenciesRepository, initialToCurrencyResolver = initialToCurrencyResolver, + demoConfig = DemoConfig(), ) } @@ -120,6 +124,15 @@ class SwapDomainModule { return IsDemoCardUseCase(config = DemoConfig()) } + @SwapScope + @Provides + @Singleton + fun provideCreateTransactionUseCase(transactionRepository: TransactionRepository): CreateTransactionUseCase { + return CreateTransactionUseCase( + transactionRepository = transactionRepository, + ) + } + @SwapScope @Provides @Singleton diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index ec0eeb5e66..b4557850a7 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -984,6 +984,18 @@ internal class StateBuilder( ) } + fun createDemoModeAlert(uiState: SwapStateHolder, onAlertClick: () -> Unit): SwapStateHolder { + return uiState.copy( + alert = SwapWarning.GenericWarning( + title = resourceReference(id = R.string.warning_demo_mode_title), + message = resourceReference(id = R.string.warning_demo_mode_message), + onClick = onAlertClick, + type = GenericWarningType.OTHER, + ), + changeCardsButtonState = ChangeCardsButtonState.ENABLED, + ) + } + private fun getProviderErrorMessage(dataError: DataError): TextReference? { return when (dataError) { is DataError.SwapsAreUnavailableNowError -> resourceReference( diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index e78235321b..2adc1f3505 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -540,6 +540,12 @@ internal class SwapViewModel @Inject constructor( is SwapTransactionState.UserCancelled -> { startLoadingQuotesFromLastState() } + is SwapTransactionState.DemoMode -> { + startLoadingQuotesFromLastState() + uiState = stateBuilder.createDemoModeAlert(uiState) { + uiState = stateBuilder.clearAlert(uiState) + } + } else -> { startLoadingQuotesFromLastState() uiState = stateBuilder.createErrorTransaction(uiState, it) { From 75e2b0b55496702fd9d63ff73cb83c474486e817 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 7 May 2024 20:27:28 +0200 Subject: [PATCH 57/98] Updated on 2026-08-14 --- .../tap/common/haptic/DefaultHapticManager.kt | 31 ++++++++++++++++ .../java/com/tangem/tap/di/HapticModule.kt | 37 +++++++++++++++++++ .../presentation/AddCustomTokenFragment.kt | 4 ++ .../AppCurrencySelectorFragment.kt | 4 ++ .../ui/appsettings/AppSettingsFragment.kt | 4 ++ .../ui/cardsettings/CardSettingsFragment.kt | 4 ++ .../AccessCodeRecoveryFragment.kt | 4 ++ .../details/ui/details/DetailsFragment.kt | 4 ++ .../details/ui/resetcard/ResetCardFragment.kt | 4 ++ .../ui/securitymode/SecurityModeFragment.kt | 4 ++ .../ui/walletconnect/WalletConnectFragment.kt | 4 ++ .../tangem/tap/features/home/HomeFragment.kt | 4 ++ .../ModalNotificationBottomSheetFragment.kt | 4 ++ .../ui/SaveWalletBottomSheetFragment.kt | 4 ++ .../impl/presentation/TokensListFragment.kt | 4 ++ .../features/welcome/ui/WelcomeFragment.kt | 4 ++ .../tangem/core/ui/haptic/HapticManager.kt | 11 ++++++ .../core/ui/haptic/MockHapticManager.kt | 12 ++++++ .../com/tangem/core/ui/res/TangemTheme.kt | 10 ++++- .../tangem/core/ui/screen/ComposeScreen.kt | 11 +++++- .../feature/qrscanning/QrScanningFragment.kt | 4 ++ .../feature/referral/ReferralFragment.kt | 4 ++ .../send/impl/presentation/SendFragment.kt | 4 ++ .../feature/swap/presentation/SwapFragment.kt | 4 ++ .../tester/presentation/TesterActivity.kt | 4 ++ .../presentation/TokenDetailsFragment.kt | 4 ++ .../wallet/presentation/WalletFragment.kt | 4 ++ 27 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/haptic/DefaultHapticManager.kt create mode 100644 app/src/main/java/com/tangem/tap/di/HapticModule.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/haptic/HapticManager.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/haptic/MockHapticManager.kt diff --git a/app/src/main/java/com/tangem/tap/common/haptic/DefaultHapticManager.kt b/app/src/main/java/com/tangem/tap/common/haptic/DefaultHapticManager.kt new file mode 100644 index 0000000000..8afeba9a48 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/haptic/DefaultHapticManager.kt @@ -0,0 +1,31 @@ +package com.tangem.tap.common.haptic + +import android.os.Build +import android.os.VibrationEffect +import android.os.Vibrator +import com.tangem.core.ui.haptic.HapticManager + +class DefaultHapticManager(private val vibrator: Vibrator) : HapticManager { + + override fun vibrateShort() { + vibrate(VIBRATION_SHORT_DURATION) + } + + override fun vibrateLong() { + vibrate(VIBRATION_MEDIUM_DURATION) + } + + private fun vibrate(durationMs: Long) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE)) + } else { + vibrator.vibrate(durationMs) + } + } + + companion object { + const val VIBRATION_SHORT_DURATION = 50L + const val VIBRATION_MEDIUM_DURATION = 100L + const val VIBRATION_LONG_DURATION = 200L + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/HapticModule.kt b/app/src/main/java/com/tangem/tap/di/HapticModule.kt new file mode 100644 index 0000000000..bf8439f09e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/HapticModule.kt @@ -0,0 +1,37 @@ +package com.tangem.tap.di + +import android.content.Context +import android.os.Build +import android.os.Vibrator +import android.os.VibratorManager +import com.tangem.tap.common.haptic.DefaultHapticManager +import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.haptic.MockHapticManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class HapticModule { + + @Provides + @Singleton + fun provideHapticManager(@ApplicationContext context: Context): HapticManager { + val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager + vibratorManager.defaultVibrator + } else { + context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + } + + return if (vibrator.hasVibrator()) { + DefaultHapticManager(vibrator = vibrator) + } else { + MockHapticManager + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt index 3900824553..efc9a7ca22 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt @@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.hilt.navigation.compose.hiltViewModel import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder @@ -25,6 +26,9 @@ internal class AddCustomTokenFragment : ComposeFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + @Composable override fun ScreenContent(modifier: Modifier) { val viewModel = hiltViewModel().apply { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorFragment.kt index b9273ef99b..0d0a7622ed 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorFragment.kt @@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder @@ -18,6 +19,9 @@ internal class AppCurrencySelectorFragment : ComposeFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + private val viewModel: AppCurrencySelectorViewModel by viewModels() @Composable diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt index 61e02daeda..22f7523945 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.navigation.NavigationAction +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.domain.appcurrency.repository.AppCurrencyRepository @@ -21,6 +22,9 @@ internal class AppSettingsFragment : ComposeFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + @Inject lateinit var appCurrencyRepository: AppCurrencyRepository diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt index 82431ec211..d9e740b39b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.fragment.app.viewModels import com.tangem.core.navigation.NavigationAction +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.features.details.redux.DetailsAction @@ -18,6 +19,9 @@ internal class CardSettingsFragment : ComposeFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + private val viewModel: CardSettingsViewModel by viewModels() @Composable diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt index 3fa69eebb7..70af8f9ca9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import com.tangem.core.navigation.NavigationAction +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.features.details.redux.DetailsState @@ -24,6 +25,9 @@ class AccessCodeRecoveryFragment : ComposeFragment(), StoreSubscriber = diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt index 2767f56545..e315a727d3 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import com.tangem.core.navigation.NavigationAction +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.features.details.redux.DetailsState @@ -19,6 +20,9 @@ internal class SecurityModeFragment : ComposeFragment(), StoreSubscriber
= diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt index 8528a2ebe0..4e69260a90 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.NavigationAction +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.common.analytics.events.WalletConnect @@ -23,6 +24,9 @@ internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber? = null diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index 90bb65ce39..a108298d21 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -13,6 +13,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.domain.tokens.TokensAction @@ -32,6 +33,9 @@ class HomeFragment : ComposeFragment(), StoreSubscriber { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + private var homeState: MutableState = mutableStateOf(store.state.homeState) override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt index ec7cb8d416..c6c6c31809 100644 --- a/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.fragment.app.activityViewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeBottomSheetFragment import com.tangem.core.ui.theme.AppThemeModeHolder @@ -24,6 +25,9 @@ internal class ModalNotificationBottomSheetFragment : ComposeBottomSheetFragment @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + private val viewModel: MainViewModel by activityViewModels() @Composable diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt index fe5792ad29..630f9ca789 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeBottomSheetFragment import com.tangem.core.ui.theme.AppThemeModeHolder @@ -27,6 +28,9 @@ internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + override val expandedHeightFraction: Float = .98f private val viewModel by viewModels() diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt index a6f68a2097..27a2ae288c 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt @@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.hilt.navigation.compose.hiltViewModel import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder @@ -25,6 +26,9 @@ internal class TokensListFragment : ComposeFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + @Composable override fun ScreenContent(modifier: Modifier) { val viewModel = hiltViewModel().apply { diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt index c3197818c0..7e1497ce01 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt @@ -16,6 +16,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.analytics.Analytics import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder @@ -33,6 +34,9 @@ internal class WelcomeFragment : ComposeFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + override fun onStart() { super.onStart() Analytics.eraseContext() diff --git a/core/ui/src/main/java/com/tangem/core/ui/haptic/HapticManager.kt b/core/ui/src/main/java/com/tangem/core/ui/haptic/HapticManager.kt new file mode 100644 index 0000000000..c4fa2906f4 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/haptic/HapticManager.kt @@ -0,0 +1,11 @@ +package com.tangem.core.ui.haptic + +import androidx.compose.runtime.Stable + +@Stable +interface HapticManager { + + fun vibrateShort() + + fun vibrateLong() +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/haptic/MockHapticManager.kt b/core/ui/src/main/java/com/tangem/core/ui/haptic/MockHapticManager.kt new file mode 100644 index 0000000000..475402f21e --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/haptic/MockHapticManager.kt @@ -0,0 +1,12 @@ +package com.tangem.core.ui.haptic + +object MockHapticManager : HapticManager { + + override fun vibrateShort() { + /** Intentionnaly do nothing */ + } + + override fun vibrateLong() { + /** Intentionnaly do nothing */ + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt index 78d91538e0..4f3287a86d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt @@ -4,6 +4,8 @@ import androidx.compose.material.Colors import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideTextStyle import androidx.compose.runtime.* +import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.haptic.MockHapticManager // TODO: use isSystemInDarkTheme() for automatic color detection internal const val IS_SYSTEM_IN_DARK_THEME: Boolean = false @@ -13,6 +15,7 @@ fun TangemTheme( isDark: Boolean = false, typography: TangemTypography = TangemTheme.typography, dimens: TangemDimens = TangemTheme.dimens, + hapticManager: HapticManager = MockHapticManager, content: @Composable () -> Unit, ) { val themeColors = if (isDark) darkThemeColors() else lightThemeColors() @@ -29,6 +32,7 @@ fun TangemTheme( LocalTangemDimens provides dimens, LocalTangemShapes provides shapes, LocalIsInDarkTheme provides isDark, + LocalHapticManager provides hapticManager, ) { ProvideTextStyle( value = TangemTheme.typography.body1, @@ -196,4 +200,8 @@ private val LocalTangemShapes = staticCompositionLocalOf { error("No TangemShapes provided") } -val LocalIsInDarkTheme = staticCompositionLocalOf { false } \ No newline at end of file +val LocalIsInDarkTheme = staticCompositionLocalOf { false } + +val LocalHapticManager = staticCompositionLocalOf { + error("No HapticManager provided") +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt index f2eade80ca..8de596d9d4 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.domain.apptheme.model.AppThemeMode @@ -26,6 +27,11 @@ internal interface ComposeScreen { */ val appThemeModeHolder: AppThemeModeHolder + /** + * Haptic manager. + */ + val hapticManager: HapticManager + /** * The screen modifier. */ @@ -55,7 +61,10 @@ internal fun ComposeScreen.createComposeView(context: Context): ComposeView { setContent { val appThemeMode by appThemeModeHolder.appThemeMode - TangemTheme(isDark = shouldUseDarkTheme(appThemeMode)) { + TangemTheme( + isDark = shouldUseDarkTheme(appThemeMode), + hapticManager = hapticManager, + ) { ScreenContent(modifier = screenModifier) } } diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt index f232473da8..9c8667962c 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt @@ -20,6 +20,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.mlkit.vision.common.InputImage +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder @@ -41,6 +42,9 @@ internal class QrScanningFragment : ComposeFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + @Inject lateinit var router: QrScanningRouter diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt index e7a122fb45..b009b1ee67 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder @@ -22,6 +23,9 @@ class ReferralFragment : ComposeFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + private val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt index da17befee3..0de48644db 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt @@ -7,6 +7,7 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder @@ -28,6 +29,9 @@ internal class SendFragment : ComposeFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + @Inject lateinit var router: SendRouter diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt index 3b16f0df21..591d7df1a7 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import com.tangem.core.navigation.ReduxNavController import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder @@ -27,6 +28,9 @@ class SwapFragment : ComposeFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + @Inject lateinit var reduxNavController: ReduxNavController diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt index 13c5799f38..b5b00b25f0 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt @@ -7,6 +7,7 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeActivity import com.tangem.core.ui.theme.AppThemeModeHolder @@ -29,6 +30,9 @@ internal class TesterActivity : ComposeActivity() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + /** Router for inner feature navigation */ @Inject lateinit var testerRouter: TesterRouter diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt index bea2fb5a9f..d1600bfa93 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.hilt.navigation.compose.hiltViewModel import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder @@ -21,6 +22,9 @@ internal class TokenDetailsFragment : ComposeFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + @Inject lateinit var tokenDetailsRouter: TokenDetailsRouter diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt index 74df363767..3434ccab80 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt @@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder @@ -23,6 +24,9 @@ internal class WalletFragment : ComposeFragment() { @Inject override lateinit var appThemeModeHolder: AppThemeModeHolder + @Inject + override lateinit var hapticManager: HapticManager + @Inject internal lateinit var manageTokensUi: ManageTokensUi From 1b2583886d83b863b951b7704885f3a9ca53edb2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 8 May 2024 12:11:09 +0200 Subject: [PATCH 58/98] Updated on 2026-08-14 --- .../common/analytics/events/AnalyticsParam.kt | 43 ++++++------- .../CardContextInterceptor.kt | 6 ++ .../core/analytics/models/AnalyticsParam.kt | 62 +++++++++---------- 3 files changed, 59 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 489effa20d..8cfba8dd64 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -13,20 +13,20 @@ sealed class AnalyticsParam { } sealed class CardBalanceState(val value: String) { - object Empty : CardBalanceState("Empty") - object Full : CardBalanceState("Full") + data object Empty : CardBalanceState("Empty") + data object Full : CardBalanceState("Full") companion object } sealed class RateApp(val value: String) { - object Liked : RateApp("Liked") - object Closed : RateApp("Close") + data object Liked : RateApp("Liked") + data object Closed : RateApp("Close") } sealed class OnOffState(val value: String) { - object On : OnOffState("On") - object Off : OnOffState("Off") + data object On : OnOffState("On") + data object Off : OnOffState("Off") companion object { @@ -35,13 +35,13 @@ sealed class AnalyticsParam { } sealed class UserCode(val value: String) { - object AccessCode : UserCode("Access Code") + data object AccessCode : UserCode("Access Code") } sealed class SecurityMode(val value: String) { - object AccessCode : SecurityMode("Access Code") - object Passcode : SecurityMode("Passcode") - object LongTap : SecurityMode("Long Tap") + data object AccessCode : SecurityMode("Access Code") + data object Passcode : SecurityMode("Passcode") + data object LongTap : SecurityMode("Long Tap") companion object { fun from(option: SecurityOption): SecurityMode = when (option) { @@ -56,8 +56,8 @@ sealed class AnalyticsParam { val key: String = "Status" - object Enabled : AccessCodeRecoveryStatus("Enabled") - object Disabled : AccessCodeRecoveryStatus("Disabled") + data object Enabled : AccessCodeRecoveryStatus("Enabled") + data object Disabled : AccessCodeRecoveryStatus("Disabled") companion object { fun from(enabled: Boolean): AccessCodeRecoveryStatus { @@ -67,21 +67,21 @@ sealed class AnalyticsParam { } sealed class Error(val value: String) { - object App : Error("App Error") - object CardSdk : Error("Card Sdk Error") - object BlockchainSdk : Error("Blockchain Sdk Error") + data object App : Error("App Error") + data object CardSdk : Error("Card Sdk Error") + data object BlockchainSdk : Error("Blockchain Sdk Error") } sealed class WalletCreationType(val value: String) { - object PrivateKey : WalletCreationType(value = "Private Key") - object NewSeed : WalletCreationType(value = "New Seed") - object SeedImport : WalletCreationType(value = "Seed Import") + data object PrivateKey : WalletCreationType(value = "Private Key") + data object NewSeed : WalletCreationType(value = "New Seed") + data object SeedImport : WalletCreationType(value = "Seed Import") } sealed class AppTheme(val value: String) { - object System : AppTheme("System") - object Dark : AppTheme("Dark") - object Light : AppTheme("Light") + data object System : AppTheme("System") + data object Dark : AppTheme("Dark") + data object Light : AppTheme("Light") companion object { fun fromAppThemeMode(mode: AppThemeMode): AppTheme { @@ -104,6 +104,7 @@ sealed class AnalyticsParam { const val PERMISSION_TYPE = "Permission Type" const val PRODUCT_TYPE = "Product Type" const val FIRMWARE = "Firmware" + const val USER_WALLET_ID = "User Wallet ID" const val CURRENCY = "Currency" const val ERROR_DESCRIPTION = "Error Description" const val ERROR_CODE = "Error Code" diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt index c881cd0127..9ba9bbbf87 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt @@ -5,6 +5,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.userwallets.UserWalletIdBuilder import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.IntroductionProcess @@ -18,6 +19,8 @@ class CardContextInterceptor( private val scanResponse: ScanResponse, ) : ParamsInterceptor { + private val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build() + override fun id(): String = CardContextInterceptor.id() override fun canBeAppliedTo(event: AnalyticsEvent): Boolean { @@ -32,6 +35,9 @@ class CardContextInterceptor( params[AnalyticsParam.BATCH] = card.batchId params[AnalyticsParam.PRODUCT_TYPE] = getProductType(scanResponse) params[AnalyticsParam.FIRMWARE] = card.firmwareVersion.stringValue + if (userWalletId != null) { + params[AnalyticsParam.USER_WALLET_ID] = userWalletId.stringValue + } ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)?.let { params[AnalyticsParam.CURRENCY] = it.value diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index a20223d2cb..eb8ef3b54b 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -3,46 +3,46 @@ package com.tangem.core.analytics.models sealed class AnalyticsParam { sealed class CardBalanceState(val value: String) { - object Empty : CardBalanceState("Empty") - object Full : CardBalanceState("Full") - object CustomToken : CardBalanceState("Custom Token") - object BlockchainError : CardBalanceState("Blockchain Error") - object NoRate : CardBalanceState("No Rate") + data object Empty : CardBalanceState("Empty") + data object Full : CardBalanceState("Full") + data object CustomToken : CardBalanceState("Custom Token") + data object BlockchainError : CardBalanceState("Blockchain Error") + data object NoRate : CardBalanceState("No Rate") companion object } sealed class TokenBalanceState(val value: String) { - object Empty : TokenBalanceState("Empty") - object Full : TokenBalanceState("Full") + data object Empty : TokenBalanceState("Empty") + data object Full : TokenBalanceState("Full") } sealed class RateApp(val value: String) { - object Liked : RateApp("Liked") - object Disliked : RateApp("Disliked") - object Closed : RateApp("Close") + data object Liked : RateApp("Liked") + data object Disliked : RateApp("Disliked") + data object Closed : RateApp("Close") } sealed class OnOffState(val value: String) { - object On : OnOffState("On") - object Off : OnOffState("Off") + data object On : OnOffState("On") + data object Off : OnOffState("Off") } sealed class OrganizeSortType(val value: String) { - object ByBalance : OrganizeSortType("By Balance") - object Manually : OrganizeSortType("Manually") + data object ByBalance : OrganizeSortType("By Balance") + data object Manually : OrganizeSortType("Manually") } sealed class UserCode(val value: String) { - object AccessCode : UserCode("Access Code") - object Passcode : UserCode("Passcode") + data object AccessCode : UserCode("Access Code") + data object Passcode : UserCode("Passcode") } sealed class AccessCodeRecoveryStatus(val value: String) { val key: String = "Status" - object Enabled : AccessCodeRecoveryStatus("Enabled") - object Disabled : AccessCodeRecoveryStatus("Disabled") + data object Enabled : AccessCodeRecoveryStatus("Enabled") + data object Disabled : AccessCodeRecoveryStatus("Disabled") companion object { fun from(enabled: Boolean): AccessCodeRecoveryStatus { @@ -52,9 +52,9 @@ sealed class AnalyticsParam { } sealed class Error(val value: String) { - object App : Error("App Error") - object CardSdk : Error("Card Sdk Error") - object BlockchainSdk : Error("Blockchain Sdk Error") + data object App : Error("App Error") + data object CardSdk : Error("Card Sdk Error") + data object BlockchainSdk : Error("Blockchain Sdk Error") } sealed class ScreensSources(val value: String) { @@ -86,8 +86,8 @@ sealed class AnalyticsParam { val permissionType: String, ) : TxSentFrom("Approve"), TxData - object WalletConnect : TxSentFrom("WalletConnect") - object Sell : TxSentFrom("Sell") + data object WalletConnect : TxSentFrom("WalletConnect") + data object Sell : TxSentFrom("Sell") } sealed interface TxData { @@ -97,10 +97,10 @@ sealed class AnalyticsParam { } sealed class FeeType(val value: String) { - object Fixed : FeeType("Fixed") - object Min : FeeType("Min") - object Normal : FeeType("Normal") - object Max : FeeType("Max") + data object Fixed : FeeType("Fixed") + data object Min : FeeType("Min") + data object Normal : FeeType("Normal") + data object Max : FeeType("Max") companion object { fun fromString(feeType: String): FeeType { @@ -116,13 +116,13 @@ sealed class AnalyticsParam { } sealed class WalletCreationType(val value: String) { - object PrivateKey : WalletCreationType("Private key") - object NewSeed : WalletCreationType("New seed") - object SeedImport : WalletCreationType("Seed import") + data object PrivateKey : WalletCreationType("Private key") + data object NewSeed : WalletCreationType("New seed") + data object SeedImport : WalletCreationType("Seed import") } sealed class WalletType(val value: String) { - object MultiCurrency : WalletType(value = "Multicurrency") + data object MultiCurrency : WalletType(value = "Multicurrency") class SingleCurrency(currencyName: String) : WalletType(currencyName) } From 4a29162e1167ae3f209bcf0d688ede8900faecd8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 7 May 2024 22:42:18 +0800 Subject: [PATCH 59/98] Updated on 2026-08-14 --- .../tap/di/domain/TransactionDomainModule.kt | 11 ++- data/transaction/build.gradle.kts | 3 + .../DefaultTransactionRepository.kt | 75 +++++++++++++++++-- .../transaction/di/TransactionDataModule.kt | 3 + .../transaction/TransactionRepository.kt | 12 +++ .../usecase/ValidateTransactionUseCase.kt | 39 ++++++++++ gradle/dependencies.toml | 2 +- 7 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/ValidateTransactionUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index bf4f00059e..0066c2ae37 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -4,10 +4,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.demo.DemoConfig import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.TransactionRepository -import com.tangem.domain.transaction.usecase.CreateTransactionUseCase -import com.tangem.domain.transaction.usecase.GetFeeUseCase -import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase -import com.tangem.domain.transaction.usecase.SendTransactionUseCase +import com.tangem.domain.transaction.usecase.* import com.tangem.domain.walletmanager.WalletManagersFacade import dagger.Module import dagger.Provides @@ -54,4 +51,10 @@ internal object TransactionDomainModule { fun provideIsFeeApproximateUseCase(feeRepository: FeeRepository): IsFeeApproximateUseCase { return IsFeeApproximateUseCase(feeRepository) } + + @Provides + @ViewModelScoped + fun provideValidateTransactionUseCase(transactionRepository: TransactionRepository): ValidateTransactionUseCase { + return ValidateTransactionUseCase(transactionRepository) + } } \ No newline at end of file diff --git a/data/transaction/build.gradle.kts b/data/transaction/build.gradle.kts index 6797014fd2..615758dcf6 100644 --- a/data/transaction/build.gradle.kts +++ b/data/transaction/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { implementation(deps.tangem.blockchain) /** Core */ + implementation(projects.core.datasource) implementation(projects.core.utils) /** Domain */ @@ -28,4 +29,6 @@ dependencies { /** DI */ implementation(deps.hilt.android) kapt(deps.hilt.kapt) + + implementation(deps.timber) } \ No newline at end of file diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index 934a88f5c9..cad609ee54 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -11,16 +11,19 @@ import com.tangem.blockchain.blockchains.ton.TonTransactionExtras import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder import com.tangem.blockchain.common.* import com.tangem.blockchain.common.transaction.Fee +import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.domain.tokens.model.Network import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext +import timber.log.Timber import java.math.BigDecimal internal class DefaultTransactionRepository( private val walletManagersFacade: WalletManagersFacade, + private val walletManagersStore: WalletManagersStore, private val coroutineDispatcherProvider: CoroutineDispatcherProvider, ) : TransactionRepository { @@ -41,17 +44,53 @@ internal class DefaultTransactionRepository( derivationPath = network.derivationPath.value, ) - val txAmount = if (isSwap) { - createAmountForSwap(amount) - } else { - amount - } - return@withContext walletManager?.createTransaction(txAmount, fee, destination)?.copy( + return@withContext walletManager?.createTransactionInternal( + amount = amount, + fee = fee, + memo = memo, + destination = destination, + network = network, + isSwap = isSwap, hash = hash, - extras = getMemoExtras(network.id.value, memo), ) } + override suspend fun validateTransaction( + amount: Amount, + fee: Fee, + memo: String?, + destination: String, + userWalletId: UserWalletId, + network: Network, + isSwap: Boolean, + hash: String?, + ): Result { + val walletManager = walletManagersStore.getSyncOrNull( + userWalletId = userWalletId, + blockchain = Blockchain.fromId(network.id.value), + derivationPath = network.derivationPath.value, + ) + + val validator = walletManager as? TransactionValidator + + return if (validator != null) { + val transaction = walletManager.createTransactionInternal( + amount = amount, + fee = fee, + memo = memo, + destination = destination, + network = network, + isSwap = isSwap, + hash = hash, + ) + + validator.validate(transaction = transaction) + } else { + Timber.e("${walletManager?.wallet?.blockchain} does not support transaction validation") + Result.success(Unit) + } + } + override suspend fun sendTransaction( txData: TransactionData, signer: CommonSigner, @@ -67,6 +106,28 @@ internal class DefaultTransactionRepository( (walletManager as TransactionSender).send(txData, signer) } + @Suppress("LongParameterList") + private fun WalletManager.createTransactionInternal( + amount: Amount, + fee: Fee, + memo: String?, + destination: String, + network: Network, + isSwap: Boolean, + hash: String?, + ): TransactionData { + val txAmount = if (isSwap) { + createAmountForSwap(amount) + } else { + amount + } + + return createTransaction(txAmount, fee, destination).copy( + hash = hash, + extras = getMemoExtras(network.id.value, memo), + ) + } + private fun getMemoExtras(networkId: String, memo: String?): TransactionExtras? { val blockchain = Blockchain.fromId(networkId) if (memo == null) return null diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/di/TransactionDataModule.kt b/data/transaction/src/main/java/com/tangem/data/transaction/di/TransactionDataModule.kt index c8949d39c0..a6d7043d91 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/di/TransactionDataModule.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/di/TransactionDataModule.kt @@ -2,6 +2,7 @@ package com.tangem.data.transaction.di import com.tangem.data.transaction.DefaultFeeRepository import com.tangem.data.transaction.DefaultTransactionRepository +import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.walletmanager.WalletManagersFacade @@ -20,10 +21,12 @@ internal object TransactionDataModule { @Singleton fun providesTransactionRepository( walletManagersFacade: WalletManagersFacade, + walletManagersStore: WalletManagersStore, coroutineDispatcherProvider: CoroutineDispatcherProvider, ): TransactionRepository { return DefaultTransactionRepository( walletManagersFacade = walletManagersFacade, + walletManagersStore = walletManagersStore, coroutineDispatcherProvider = coroutineDispatcherProvider, ) } diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt index c28e9dbea7..4acf080799 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt @@ -22,6 +22,18 @@ interface TransactionRepository { hash: String?, ): TransactionData? + @Suppress("LongParameterList") + suspend fun validateTransaction( + amount: Amount, + fee: Fee, + memo: String?, + destination: String, + userWalletId: UserWalletId, + network: Network, + isSwap: Boolean, + hash: String?, + ): Result + suspend fun sendTransaction( txData: TransactionData, signer: CommonSigner, diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/ValidateTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/ValidateTransactionUseCase.kt new file mode 100644 index 0000000000..56c89954a4 --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/ValidateTransactionUseCase.kt @@ -0,0 +1,39 @@ +package com.tangem.domain.transaction.usecase + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.transaction.TransactionRepository +import com.tangem.domain.wallets.models.UserWalletId + +class ValidateTransactionUseCase( + private val transactionRepository: TransactionRepository, +) { + + @Suppress("LongParameterList") + suspend operator fun invoke( + amount: Amount, + fee: Fee, + memo: String?, + destination: String, + userWalletId: UserWalletId, + network: Network, + isSwap: Boolean = false, + hash: String? = null, + ): Either { + return transactionRepository.validateTransaction( + amount = amount, + fee = fee, + memo = memo, + destination = destination, + userWalletId = userWalletId, + network = network, + isSwap = isSwap, + hash = hash, + ) + .fold(onSuccess = { Unit.right() }, onFailure = { it.left() }) + } +} \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 6e2f105776..7478a68a2b 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -86,7 +86,7 @@ room = "2.6.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-621" +tangemBlockchainSdk = "develop-624" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-351" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 1716318447b7a6a053dc7d7f1d82dad55a328c47 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 May 2024 23:23:11 +0300 Subject: [PATCH 60/98] Updated on 2026-08-14 --- .../utils/TokenListAnalyticsSender.kt | 2 +- .../utils/WalletWarningsAnalyticsSender.kt | 2 +- .../state/model/WalletPullToRefreshConfig.kt | 8 +++- .../state/utils/WalletLoadingStateFactory.kt | 2 +- .../presentation/wallet/ui/WalletScreen.kt | 12 +++-- .../wallet/utils/ScreenLifecycleProvider.kt | 10 +++-- .../wallet/viewmodels/WalletViewModel.kt | 44 +++++++++++++++++-- .../viewmodels/intents/WalletClickIntents.kt | 16 +++---- 8 files changed, 74 insertions(+), 22 deletions(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt index 23f6a0c60d..f0ca57a2b3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt @@ -33,7 +33,7 @@ internal class TokenListAnalyticsSender @Inject constructor( private val mutex = Mutex() suspend fun send(displayedUiState: WalletState?, userWallet: UserWallet, tokenList: TokenList) { - if (screenLifecycleProvider.isBackground) return + if (screenLifecycleProvider.isBackgroundState.value) return if (displayedUiState == null || displayedUiState.pullToRefreshConfig.isRefreshing) return if (tokenList.totalFiatBalance is TokenList.FiatBalance.Loading) return diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt index e80fc87a24..ae05161912 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt @@ -16,7 +16,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor( ) { fun send(displayedUiState: WalletState?, newWarnings: List) { - if (screenLifecycleProvider.isBackground) return + if (screenLifecycleProvider.isBackgroundState.value) return if (newWarnings.isEmpty()) return if (displayedUiState == null || displayedUiState.pullToRefreshConfig.isRefreshing) return diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletPullToRefreshConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletPullToRefreshConfig.kt index ca91846374..8bf0e4ea63 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletPullToRefreshConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletPullToRefreshConfig.kt @@ -6,4 +6,10 @@ package com.tangem.feature.wallet.presentation.wallet.state.model * @property isRefreshing state is indicator visible * @property onRefresh lambda be invoked when pulled to refresh */ -data class WalletPullToRefreshConfig(val isRefreshing: Boolean, val onRefresh: () -> Unit) \ No newline at end of file +data class WalletPullToRefreshConfig(val isRefreshing: Boolean, val onRefresh: (ShowRefreshState) -> Unit) { + + @JvmInline + value class ShowRefreshState( + val value: Boolean, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt index 2dc37ce327..71f0bdd4c4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt @@ -72,7 +72,7 @@ internal class WalletLoadingStateFactory(private val clickIntents: WalletClickIn } private fun createPullToRefreshConfig(): WalletPullToRefreshConfig { - return WalletPullToRefreshConfig(onRefresh = clickIntents::onRefreshSwipe, isRefreshing = false) + return WalletPullToRefreshConfig(onRefresh = { clickIntents.onRefreshSwipe(it.value) }, isRefreshing = false) } private fun UserWallet.toLoadingWalletCardState(): WalletCardState { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index cfeccc9db0..670f85b03a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -135,7 +135,9 @@ private fun WalletContent( .padding(horizontal = horizontalPadding) LazyColumn( - modifier = Modifier.fillMaxSize().testTag(TestTags.WALLET_SCREEN), + modifier = Modifier + .fillMaxSize() + .testTag(TestTags.WALLET_SCREEN), contentPadding = PaddingValues( top = TangemTheme.dimens.spacing8, bottom = TangemTheme.dimens.spacing92, @@ -317,7 +319,9 @@ private fun BaseScaffoldManageTokenRedesign( content = { paddingValues -> val pullRefreshState = rememberPullRefreshState( refreshing = selectedWallet.pullToRefreshConfig.isRefreshing, - onRefresh = selectedWallet.pullToRefreshConfig.onRefresh, + onRefresh = { + selectedWallet.pullToRefreshConfig.onRefresh(WalletPullToRefreshConfig.ShowRefreshState(true)) + }, ) Column( @@ -503,7 +507,9 @@ private fun BaseScaffold( content = { val pullRefreshState = rememberPullRefreshState( refreshing = selectedWallet.pullToRefreshConfig.isRefreshing, - onRefresh = selectedWallet.pullToRefreshConfig.onRefresh, + onRefresh = { + selectedWallet.pullToRefreshConfig.onRefresh(WalletPullToRefreshConfig.ShowRefreshState(true)) + }, ) Box( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/ScreenLifecycleProvider.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/ScreenLifecycleProvider.kt index 5ebccf6c11..f2c721ac87 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/ScreenLifecycleProvider.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/ScreenLifecycleProvider.kt @@ -3,19 +3,21 @@ package com.tangem.feature.wallet.presentation.wallet.utils import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @ViewModelScoped internal class ScreenLifecycleProvider @Inject constructor() : DefaultLifecycleObserver { - var isBackground: Boolean = true - private set + private val _isBackgroundState = MutableStateFlow(false) + val isBackgroundState: StateFlow = _isBackgroundState override fun onResume(owner: LifecycleOwner) { - isBackground = false + _isBackgroundState.value = false } override fun onPause(owner: LifecycleOwner) { - isBackground = true + _isBackgroundState.value = true } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index e74ecbdb0b..775bb89a39 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -20,6 +20,7 @@ import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContent import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent.DemonstrateWalletsScrollPreview.Direction +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletPullToRefreshConfig import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState import com.tangem.feature.wallet.presentation.wallet.state.transformers.* import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender @@ -31,7 +32,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.delay +import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -61,7 +62,9 @@ internal class WalletViewModel @Inject constructor( val uiState: StateFlow = stateHolder.uiState private lateinit var router: InnerWalletRouter - private var walletsUpdateJobHolder: JobHolder = JobHolder() + private val walletsUpdateJobHolder = JobHolder() + private val refreshWalletJobHolder = JobHolder() + private var needToRefreshWallet = false init { analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpened) @@ -71,6 +74,7 @@ internal class WalletViewModel @Inject constructor( subscribeToUserWalletsUpdates() subscribeOnBalanceHiding() subscribeOnSelectedWalletFlow() + subscribeToScreenBackgroundState() } fun setWalletRouter(router: InnerWalletRouter) { @@ -150,6 +154,36 @@ internal class WalletViewModel @Inject constructor( } } + // We need to update the current wallet if the application was in the background for more than 10 seconds + // and then returned to the foreground + private fun subscribeToScreenBackgroundState() { + screenLifecycleProvider.isBackgroundState + .onEach { isBackground -> + refreshWalletJobHolder.cancel() + when { + isBackground -> needToRefreshTimer() + needToRefreshWallet && !isBackground -> triggerRefreshWallet() + } + } + .launchIn(viewModelScope) + } + + private fun needToRefreshTimer() { + viewModelScope.launch { + delay(REFRESH_WALLET_BACKGROUND_TIMER_MILLIS) + needToRefreshWallet = true + }.saveIn(refreshWalletJobHolder) + } + + private fun triggerRefreshWallet() { + needToRefreshWallet = false + val state = stateHolder.uiState.value + val wallet = state.wallets.getOrNull(state.selectedWalletIndex) ?: return + wallet.pullToRefreshConfig.onRefresh.invoke( + WalletPullToRefreshConfig.ShowRefreshState(false), + ) + } + private suspend fun updateWallets(action: WalletsUpdateActionResolver.Action) { when (action) { is WalletsUpdateActionResolver.Action.InitializeWallets -> initializeWallets(action) @@ -280,7 +314,7 @@ internal class WalletViewModel @Inject constructor( } private fun closeScreen(screen: AppScreen) { - if (!screenLifecycleProvider.isBackground) { + if (!screenLifecycleProvider.isBackgroundState.value) { stateHolder.clear() router.popBackStack(screen = screen) } @@ -296,4 +330,8 @@ internal class WalletViewModel @Inject constructor( ), ) } + + private companion object { + const val REFRESH_WALLET_BACKGROUND_TIMER_MILLIS = 10000L + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt index 3cf99441ab..3266406422 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt @@ -77,17 +77,17 @@ internal class WalletClickIntents @Inject constructor( } } - fun onRefreshSwipe() { + fun onRefreshSwipe(showRefreshState: Boolean) { when (stateHolder.getSelectedWallet()) { is WalletState.MultiCurrency.Content -> { analyticsEventHandler.send(PortfolioEvent.Refreshed) - refreshMultiCurrencyContent() + refreshMultiCurrencyContent(showRefreshState) } is WalletState.SingleCurrency.Content, is WalletState.Visa.Content, -> { analyticsEventHandler.send(PortfolioEvent.Refreshed) - refreshSingleCurrencyContent() + refreshSingleCurrencyContent(showRefreshState) } is WalletState.MultiCurrency.Locked, is WalletState.SingleCurrency.Locked, @@ -97,14 +97,14 @@ internal class WalletClickIntents @Inject constructor( } fun onReloadClick() { - refreshSingleCurrencyContent() + refreshSingleCurrencyContent(showRefreshState = true) } - private fun refreshMultiCurrencyContent() { + private fun refreshMultiCurrencyContent(showRefreshState: Boolean) { val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return stateHolder.update( - SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = true), + SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = showRefreshState), ) viewModelScope.launch(dispatchers.main) { @@ -126,11 +126,11 @@ internal class WalletClickIntents @Inject constructor( // FIXME: refreshSingleCurrencyContent mustn't update the TxHistory and Buttons. It only must fetch primary // currency. Now it not works because GetPrimaryCurrency's subscriber uses .distinctUntilChanged() - private fun refreshSingleCurrencyContent() { + private fun refreshSingleCurrencyContent(showRefreshState: Boolean) { val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return stateHolder.update( - SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = true), + SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = showRefreshState), ) viewModelScope.launch(dispatchers.main) { From 5b97cad9430e4e570febadf9d35a03273df399a9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 May 2024 23:35:38 +0800 Subject: [PATCH 61/98] Updated on 2026-08-14 --- .../main/java/com/tangem/datasource/config/ConfigManagerImpl.kt | 1 + .../main/java/com/tangem/datasource/config/models/JsonModels.kt | 1 + .../blockchainsdk/converters/BlockchainSDKConfigConverter.kt | 1 + 3 files changed, 3 insertions(+) diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt index f7565b73c2..fe5ad1ad8e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt @@ -99,6 +99,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager { ), chiaFireAcademyApiKey = configValues.chiaFireAcademyApiKey, chiaTangemApiKey = configValues.chiaTangemApiKey, + polygonScanApiKey = configValues.polygonScanApiKey, ), amplitudeApiKey = configValues.amplitudeApiKey, sprinklr = configValues.sprinklr, diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt index 5d2560be9e..91ae26a0d4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt @@ -41,6 +41,7 @@ class ConfigValueModel( val chiaTangemApiKey: String?, val devExpress: ExpressModel?, val express: ExpressModel?, + val polygonScanApiKey: String?, ) @JsonClass(generateAdapter = true) diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt index ff8c190e0e..7fd86c9aa1 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt @@ -37,6 +37,7 @@ internal object BlockchainSDKConfigConverter : Converter Date: Tue, 14 May 2024 11:55:45 +0200 Subject: [PATCH 62/98] Updated on 2026-08-14 --- .../java/com/tangem/tap/di/ThemeModule.kt | 6 +++-- .../com/tangem/tap/di/UiDependenciesModule.kt | 24 +++++++++++++++++++ .../presentation/AddCustomTokenFragment.kt | 8 ++----- .../AppCurrencySelectorFragment.kt | 8 ++----- .../ui/appsettings/AppSettingsFragment.kt | 8 ++----- .../ui/cardsettings/CardSettingsFragment.kt | 8 ++----- .../AccessCodeRecoveryFragment.kt | 8 ++----- .../details/ui/details/DetailsFragment.kt | 8 ++----- .../details/ui/resetcard/ResetCardFragment.kt | 8 ++----- .../ui/securitymode/SecurityModeFragment.kt | 8 ++----- .../ui/walletconnect/WalletConnectFragment.kt | 8 ++----- .../tangem/tap/features/home/HomeFragment.kt | 8 ++----- .../ModalNotificationBottomSheetFragment.kt | 8 ++----- .../ui/SaveWalletBottomSheetFragment.kt | 8 ++----- .../impl/presentation/TokensListFragment.kt | 8 ++----- .../features/welcome/ui/WelcomeFragment.kt | 8 ++----- .../java/com/tangem/core/ui/UiDependencies.kt | 11 +++++++++ .../tangem/core/ui/screen/ComposeScreen.kt | 16 ++++--------- .../feature/qrscanning/QrScanningFragment.kt | 8 ++----- .../feature/referral/ReferralFragment.kt | 8 ++----- .../send/impl/presentation/SendFragment.kt | 8 ++----- .../feature/swap/presentation/SwapFragment.kt | 8 ++----- .../tester/presentation/TesterActivity.kt | 8 ++----- .../presentation/TokenDetailsFragment.kt | 8 ++----- .../wallet/presentation/WalletFragment.kt | 8 ++----- 25 files changed, 86 insertions(+), 139 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt diff --git a/app/src/main/java/com/tangem/tap/di/ThemeModule.kt b/app/src/main/java/com/tangem/tap/di/ThemeModule.kt index af2732b2d9..e4b2deeb8d 100644 --- a/app/src/main/java/com/tangem/tap/di/ThemeModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ThemeModule.kt @@ -5,13 +5,15 @@ import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton @Module -@InstallIn(ActivityComponent::class) +@InstallIn(SingletonComponent::class) internal object ThemeModule { @Provides + @Singleton fun provideAppThemeModeHolder(): AppThemeModeHolder { return MutableAppThemeModeHolder } diff --git a/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt b/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt new file mode 100644 index 0000000000..97b0f48bee --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt @@ -0,0 +1,24 @@ +package com.tangem.tap.di + +import com.tangem.core.ui.UiDependencies +import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.theme.AppThemeModeHolder +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object UiDependenciesModule { + + @Provides + @Singleton + fun provideUiDependencies(hapticManager: HapticManager, appThemeModeHolder: AppThemeModeHolder): UiDependencies { + return object : UiDependencies { + override val hapticManager = hapticManager + override val appThemeModeHolder = appThemeModeHolder + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt index efc9a7ca22..da295c57c5 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt @@ -5,11 +5,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.hilt.navigation.compose.hiltViewModel +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.SystemBarsEffect -import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenScreen import com.tangem.tap.features.customtoken.impl.presentation.viewmodels.AddCustomTokenViewModel import dagger.hilt.android.AndroidEntryPoint @@ -24,10 +23,7 @@ import javax.inject.Inject internal class AddCustomTokenFragment : ComposeFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies @Composable override fun ScreenContent(modifier: Modifier) { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorFragment.kt index 0d0a7622ed..d48050ec85 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorFragment.kt @@ -5,11 +5,10 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.SystemBarsEffect -import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -17,10 +16,7 @@ import javax.inject.Inject internal class AppCurrencySelectorFragment : ComposeFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies private val viewModel: AppCurrencySelectorViewModel by viewModels() diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt index 22f7523945..2efc912cca 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt @@ -7,9 +7,8 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.navigation.NavigationAction -import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.store @@ -20,10 +19,7 @@ import javax.inject.Inject internal class AppSettingsFragment : ComposeFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies @Inject lateinit var appCurrencyRepository: AppCurrencyRepository diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt index d9e740b39b..c81e2d819e 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt @@ -5,9 +5,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.fragment.app.viewModels import com.tangem.core.navigation.NavigationAction -import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.store import dagger.hilt.android.AndroidEntryPoint @@ -17,10 +16,7 @@ import javax.inject.Inject internal class CardSettingsFragment : ComposeFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies private val viewModel: CardSettingsViewModel by viewModels() diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt index 70af8f9ca9..7a365126b6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt @@ -5,9 +5,8 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import com.tangem.core.navigation.NavigationAction -import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.store import dagger.hilt.android.AndroidEntryPoint @@ -23,10 +22,7 @@ class AccessCodeRecoveryFragment : ComposeFragment(), StoreSubscriber { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies @Inject lateinit var walletsRepository: WalletsRepository diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt index 39c6afd544..1522c07ed3 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt @@ -5,9 +5,8 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import com.tangem.core.navigation.NavigationAction -import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.store import dagger.hilt.android.AndroidEntryPoint @@ -18,10 +17,7 @@ import javax.inject.Inject internal class ResetCardFragment : ComposeFragment(), StoreSubscriber { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies private val viewModel = ResetCardViewModel(store) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt index e315a727d3..c44b9ca3fa 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt @@ -5,9 +5,8 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import com.tangem.core.navigation.NavigationAction -import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.store import dagger.hilt.android.AndroidEntryPoint @@ -18,10 +17,7 @@ import javax.inject.Inject internal class SecurityModeFragment : ComposeFragment(), StoreSubscriber { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies private val viewModel = SecurityModeViewModel(store) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt index 4e69260a90..19dd0771bc 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt @@ -8,9 +8,8 @@ import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.NavigationAction -import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState import com.tangem.tap.store @@ -22,10 +21,7 @@ import javax.inject.Inject internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies private val viewModel: WalletConnectViewModel by viewModels() diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index a108298d21..8e081474fb 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -12,10 +12,9 @@ import androidx.lifecycle.lifecycleScope import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.SystemBarsEffect -import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.domain.tokens.TokensAction import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.redux.AppState @@ -31,10 +30,7 @@ import javax.inject.Inject class HomeFragment : ComposeFragment(), StoreSubscriber { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies private var homeState: MutableState = mutableStateOf(store.state.homeState) diff --git a/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt index c6c6c31809..ef72720cad 100644 --- a/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt @@ -9,10 +9,9 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.fragment.app.activityViewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeBottomSheetFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.features.main.MainViewModel import com.tangem.tap.features.main.model.ModalNotification import com.tangem.tap.features.main.ui.components.ModalNotificationContent @@ -23,10 +22,7 @@ import javax.inject.Inject internal class ModalNotificationBottomSheetFragment : ComposeBottomSheetFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies private val viewModel: MainViewModel by activityViewModels() diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt index 630f9ca789..d0d7c64fb5 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt @@ -11,10 +11,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeBottomSheetFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.features.details.ui.cardsettings.resolveReference import com.tangem.tap.features.saveWallet.ui.components.EnrollBiometricsDialogContent import com.tangem.tap.features.saveWallet.ui.components.SaveWalletScreenContent @@ -26,10 +25,7 @@ import javax.inject.Inject internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies override val expandedHeightFraction: Float = .98f diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt index 27a2ae288c..272c773c95 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt @@ -5,11 +5,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.hilt.navigation.compose.hiltViewModel +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.SystemBarsEffect -import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.features.tokens.impl.presentation.ui.TokensListScreen import com.tangem.tap.features.tokens.impl.presentation.viewmodels.TokensListViewModel import dagger.hilt.android.AndroidEntryPoint @@ -24,10 +23,7 @@ import javax.inject.Inject internal class TokensListFragment : ComposeFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies @Composable override fun ScreenContent(modifier: Modifier) { diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt index 7e1497ce01..60ab61900f 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt @@ -15,11 +15,10 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.analytics.Analytics +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.SystemBarsEffect -import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.tap.common.analytics.events.SignIn import com.tangem.tap.common.extensions.eraseContext import com.tangem.tap.features.details.ui.cardsettings.resolveReference @@ -32,10 +31,7 @@ import javax.inject.Inject internal class WelcomeFragment : ComposeFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies override fun onStart() { super.onStart() diff --git a/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt b/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt new file mode 100644 index 0000000000..62e19d4af6 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt @@ -0,0 +1,11 @@ +package com.tangem.core.ui + +import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.theme.AppThemeModeHolder + +interface UiDependencies { + + val hapticManager: HapticManager + + val appThemeModeHolder: AppThemeModeHolder +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt index 8de596d9d4..4c2faae280 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt @@ -8,9 +8,8 @@ import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView -import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.domain.apptheme.model.AppThemeMode /** @@ -23,14 +22,9 @@ import com.tangem.domain.apptheme.model.AppThemeMode internal interface ComposeScreen { /** - * The holder for managing the current application theme mode. + * The holder for ui dependencies. */ - val appThemeModeHolder: AppThemeModeHolder - - /** - * Haptic manager. - */ - val hapticManager: HapticManager + val uiDependencies: UiDependencies /** * The screen modifier. @@ -59,11 +53,11 @@ internal interface ComposeScreen { internal fun ComposeScreen.createComposeView(context: Context): ComposeView { return ComposeView(context).apply { setContent { - val appThemeMode by appThemeModeHolder.appThemeMode + val appThemeMode by uiDependencies.appThemeModeHolder.appThemeMode TangemTheme( isDark = shouldUseDarkTheme(appThemeMode), - hapticManager = hapticManager, + hapticManager = uiDependencies.hapticManager, ) { ScreenContent(modifier = screenModifier) } diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt index 9c8667962c..72d725c460 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt @@ -20,10 +20,9 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.mlkit.vision.common.InputImage -import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.feature.qrscanning.inner.MLKitBarcodeAnalyzer import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter import com.tangem.feature.qrscanning.presentation.QrScanningContent @@ -40,10 +39,7 @@ import kotlin.properties.Delegates internal class QrScanningFragment : ComposeFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies @Inject lateinit var router: QrScanningRouter diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt index b009b1ee67..2ccd4b74fc 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt @@ -5,11 +5,10 @@ import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.SystemBarsEffect -import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.feature.referral.router.ReferralRouter import com.tangem.feature.referral.ui.ReferralScreen import com.tangem.feature.referral.viewmodels.ReferralViewModel @@ -21,10 +20,7 @@ import javax.inject.Inject class ReferralFragment : ComposeFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies private val viewModel by viewModels() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt index 0de48644db..1c7d7f9cee 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt @@ -6,11 +6,10 @@ import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.SystemBarsEffect -import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.send.impl.navigation.InnerSendRouter import com.tangem.features.send.impl.presentation.state.StateRouter @@ -27,10 +26,7 @@ import javax.inject.Inject internal class SendFragment : ComposeFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies @Inject lateinit var router: SendRouter diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt index 591d7df1a7..6409755135 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapFragment.kt @@ -6,11 +6,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import com.tangem.core.navigation.ReduxNavController +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.SystemBarsEffect -import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.feature.swap.router.CustomTabsManager import com.tangem.feature.swap.router.SwapNavScreen import com.tangem.feature.swap.router.SwapRouter @@ -26,10 +25,7 @@ import javax.inject.Inject class SwapFragment : ComposeFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies @Inject lateinit var reduxNavController: ReduxNavController diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt index b5b00b25f0..2ddfba7af0 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt @@ -6,11 +6,10 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.SystemBarsEffect -import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeActivity -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.feature.tester.presentation.actions.TesterActionsScreen import com.tangem.feature.tester.presentation.actions.TesterActionsViewModel import com.tangem.feature.tester.presentation.featuretoggles.ui.FeatureTogglesScreen @@ -28,10 +27,7 @@ import javax.inject.Inject internal class TesterActivity : ComposeActivity() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies /** Router for inner feature navigation */ @Inject diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt index d1600bfa93..94200fbd59 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt @@ -4,11 +4,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.hilt.navigation.compose.hiltViewModel +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.SystemBarsEffect -import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreen import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsViewModel @@ -20,10 +19,7 @@ import javax.inject.Inject internal class TokenDetailsFragment : ComposeFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies @Inject lateinit var tokenDetailsRouter: TokenDetailsRouter diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt index 3434ccab80..5a2c923015 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt @@ -2,11 +2,10 @@ package com.tangem.feature.wallet.presentation import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.SystemBarsEffect -import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.features.managetokens.navigation.ManageTokensUi import com.tangem.features.wallet.navigation.WalletRouter @@ -22,10 +21,7 @@ import javax.inject.Inject internal class WalletFragment : ComposeFragment() { @Inject - override lateinit var appThemeModeHolder: AppThemeModeHolder - - @Inject - override lateinit var hapticManager: HapticManager + override lateinit var uiDependencies: UiDependencies @Inject internal lateinit var manageTokensUi: ManageTokensUi From 11cf593007fd69bfa46c11cca2e1e3053d3c8226 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 May 2024 13:48:13 +0800 Subject: [PATCH 63/98] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 1436 +++++++++-------- core/res/src/main/res/values/strings.xml | 1432 ++++++++-------- .../presentation/state/SendNotification.kt | 24 + .../state/confirm/SendNotificationFactory.kt | 137 +- .../presentation/viewmodel/SendViewModel.kt | 7 +- .../com/tangem/lib/crypto/BlockchainUtils.kt | 5 + 6 files changed, 1585 insertions(+), 1456 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index b8a5d12fe9..7d27d1a437 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1,720 +1,722 @@ - Добавить токен - Валюты - Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств. - Обратиться в поддержку - Попробовать снова - Эта функция недоступна в демонстрационном режиме - Причина: %s - Не могу отправить транзакцию - Выбранный кошелёк не поддерживает сеть %1$s - Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. - Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. - Спасибо за ваш отзыв. Мы ответим в кратчайшие сроки. - Ваши предложения отправлены - Пожалуйста, попробуйте приложить карту в точности, как показано на анимации, или запросите поддержку. - У вас возникли трудности со сканированием карты? - Эта карта не предназначена для работы с этим приложением - Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. - Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem - Включите биометрическую аутентификацию - Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. - При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены из приложения. - Сохранение кода доступа - Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. - Cохранение кошелька - Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. - Тёмная - Светлая - Как в системе - При выборе настройки как в системе приложение будет использовать тему в соответствии с настройками вашего устройства - Системная - Тема - Настройки приложения - Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" - Больше не показывать - Понятно - Балансы скрыты - Пожалуйста, отсканируйте карту - Пожалуйста, попробуйте снова через 30 секунд или отсканируйте карту - Слишком много попыток - Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона. - Начать резервное копирование - - %d карта - %d карты - %d карт - %d карт - - Отключите эту опцию, если не хотите, чтобы эта карта использовалась для сброса кодов доступа на другие карты этого кошелька. Обратите внимание, сброс кода также не будет доступен на этой карте. - Использовать эту карту для сброса кода доступа на других картах в этом кошельке - Восстановление кода доступа - Сбросить - Вы уверены, что хотите это сделать? - Смена кода доступа - Код доступа будет изменен только на данной карте - Заводские настройки - Тип безопасности - Настройки карты - Ввиду особенностей сети Cardano при транзакции токена %1$s помимо комиссии сети будет списано %2$s - Чтобы совершить транзакцию %1$s, вы должны внести некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA. (рекомендуется баланс в размере 5 ADA) - Недостаточно ADA для отправки токена - Сеть Cardano не позволяет вам оставлять на своем балансе сумму ниже минимального значения %1$s, если у вас есть токены. Пожалуйста, убедитесь, что у вас осталось немного ADA. - Принять - Доступ запрещен - Применить - Одобрение - Внимание - Баланс: %s - Баланс - биометрическую аутентификацию - биометрией - Купить - Перейти на %1$s - Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. - Отмена - Закрыть - Продолжить - Копировать - Скопировать адрес - Создать - Удалить - Отключено - Отключить - Готово - Включить - Включено - Ошибка - Обозреватель - Посмотреть историю транзакций - Обозреватель - Комиссия - Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s - Свое - Быстро - По рынку - Медленно - Скорость и комиссия - Получить адреса - К провайдеру - Импортировать - Заблокирован - Основная сеть - Сетевая комиссия - Далее - Нет - Нет адреса - OK - Основная карта - Парольная фраза - Вставить - Подробнее - Получить - Отклонить - Перезагрузить - Переименовать - Повторить - Сохранить изменения - Искать - Поиск токенов - Seed-фраза - Выберите действие - Продать - Отправить - Сервер недоступен, повторите попытку позднее - Поделиться - Подписать - Подписать и отправить - Начать - Отправить - Успешно - Поддержка - Обмен - условия участия - Ошибка транзакции - Транзакции - Перевод - Я понял - Произошла ошибка. Пожалуйста, попробуйте снова. - Недоступно - Предупреждение - Да - Адрес контракта скопирован! - Доступные сети - Добавить токен - Адрес контракта - Адрес контракта некорректен - Пожалуйста, выберите сеть - Десятичное число должно быть действительным целым числом, до %li - Своя деривация - Например m/00\'/0000\'/0\'/0/0 - Введите свою деривацию - Знаков после запятой - Путь деривации - По умолчанию - Деривация по BIP44 - Введенный путь деривации некорректен - Например, USD Coin - Название токена - Не выбрано - Сеть - Сеть - Вы можете добавить токен в ручную, если он не поддерживается Tangem - Например, USDC - Символ - Символ токена - Этот токен/сеть уже находится в вашем списке - Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить - Остерегайтесь мошеннических токенов, они могут ничего не стоить - Токены могут быть созданы кем угодно - Чат - Код доступа - Перед сканированием карты вам нужно будет ввести правильный код доступа. - Задержка сканирования - Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. - Пароль - Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. - Реферальная программа - Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы - %s хэшей - Номер карты - Обратиться в поддержку - Добавить еще карты - Валюта приложения - Скрывать балансы жестом переворота - Эмитент - Подписано - Если вы забудете код, то потеряете доступ к своим средствам. Восстановление кода невозможно. - Подробности - Проверьте подключение с интернетом или переключитесь на другую сеть - Условия использования - Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. - Мои токены - У вас нет добавленных токенов. Добавьте токены для обмена - Недоступен для обмена с %s - Предоставлено - Статус - Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами - Выберите провайдера - Произошла ошибка. Код: %s - К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) - Выбранный провайдер недоступен для обмена. Попробуйте позже. (Код: %s) - В данный момент обмен невозможен. Попробуйте позже. (Код: %s) - Курс обмена - Обмен через %s - Чтобы вернуть ваши деньги, посетите сайт провайдера - Операция не выполнена провайдером - Посетите сайт провайдера для проверки - Провайдер запрашивает прохождение верификации - Отменен - Подтверждено - Подтверждение - Подтверждение... - Обменяно - Обмен - Обмен... - Неудачно - Депозит получен - Ожидание депозита - Ожидаем пополнения... - Возвращено - Отправляем - Отправка средств... - Отправлено - Данные провайдера. Сумма к получению может измениться в зависимости от рыночных условий. - Статус обмена - Требуется верификация - Список токенов в вашем кошельке - Получение наилучших курсов... - Плавающая ставка - Пользуясь сервисом, вы соглашаетесь с %s - Пользуясь сервисом, вы соглашаетесь с %1$s и %2$s - Больше провайдеров на подходе.\nСледите за обновлениями! - Политикой конфиденциальности - Провайдер - Лучший курс - Доступно до %s - Доступно с %s - Недоступно для этой пары - Требуется разрешение - Условиями использования - Токены не найдены. Пожалуйста, попробуйте другой запрос - ID транзакции скопирован - Информация ниже не является обязательной. Вы можете стереть её, если хотите. - Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. - Скажите, пожалуйста, какая у вас карта? - Привет, команда поддержки, - Пожалуйста, расскажите нам больше о вашей проблеме. Каждая маленькая деталь может помочь. - Мои предложения - Не могу отсканировать карту - Обращение в поддержку - Обращение в поддержку Tangem - Не могу отправить транзакцию - Купить - Сканировать - Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции - Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции - Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции - Приложите, чтобы отсканировать - Приложите, чтобы подписать - Приложите карту - Вы обновили данные биометрии, отсканируйте свою карту для входа - Чтобы начать отслеживать свои криптоактивы и транзакции, добавьте токены - Управление токенами - Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту - Отсканируйте карту - Обменивайте свои токены с %1$s комиссии провайдера через Changelly с %2$s по %3$s февраля. - Обмен с Changelly, %s комиссии - Токены - Добавить - Изменить - Рыночная капитализация - Основная сеть - Не основной или основной блокчейн, на котором размещен токен - Не основные сети - Выберите сети - Кошелек - Не удалось найти этот токен, вы можете добавить его вручную. - - %1$d из %2$d кошелька - %1$d из %2$d кошельков - %1$d из %2$d кошельков - %1$d из %2$d кошельков - - например Bitcoin - Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление. - Голосовать - Выберите кошелек - Кошелёк не поддерживает более одной сети - Вам необходимо установить единый код доступа для защиты всех ваших карт - Защита - Позже вы сможете установить индивидуальный код доступа для каждой карты - Персонализация - Код доступа можно восстановить с помощью привязанной карты. Не храните все карты в одном месте. - Восстановление - Выберите любое слово, фразу или число в качестве кода доступа - Создайте код доступа - Введите код доступа еще раз, чтобы избежать ошибки - Повторно введите код доступа - Код доступа должен состоять не менее чем из 4 символов. - Введенные коды доступа не совпадают - Необходимо повторить операцию, при этом карта будет сброшена к заводским настройкам - Ошибка активации - Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? - Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. - Парольная фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. - Добавить резервную карту - Сканировать карту #%d - Создать резервную копию - Сканировать основную карту - Перейти к моему кошельку - Завершение бэкапа - Получить криптовалюту - Сканировать основную карту - Пропустить - Как это работает? - Давайте сгенерируем все ключи на вашей карте и создадим безопасный кошелек - Создать кошелек - Создать кошелек - Другие опции - Ваши ключи будут надежно сгенерированы внутри карты. Никакой seed-фразы, а это значит, что никто не может экспортировать или украсть ее. - Cоздавайте ключи приватно - Ваша карта активирована и готова к использованию - Успешно! - В этом случае вам будет необходимо начать процесс заново. - Вы хотите выйти из процесса активации? - Подготовка - Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Если на нем есть средства, пожалуйста сначала выведите их, а затем сделайте сброс до заводских настроек и используйте как резервную. - Резервная копия - Прочитать о seed-фразе - - - Запишите эти %d слова в порядке, указанном ниже, и сохраните их в надежном месте. - Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. - Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. - - Ваша seed-фраза - - - %d слова - %d слов - %d слов - - Чтобы импортировать кошелек, введите seed-фразу в поле ниже - Создать seed-фразу - Импорт кошелька - Seed-фраза — это набор слов, который дает возможность восстановить кошелек. В отличие от ключей, сгенерированных картой, seed-фраза не защищена и может быть скопирована и украдена. Используйте этот вариант на свой страх и риск. - Использовать seed-фразу - Неверная seed-фраза. Пожалуйста, проверьте порядок слов. - Неверная seed-фраза. Пожалуйста, проверьте орфографию. - Устаревший - Чтобы проверить, правильно ли вы записали seed-фразу, введите 2-е, 7-е и 11-е слова - Итак, проверим - Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. - Вы можете добавить еще одну карту или завершить процесс резервного копирования - Подготовьте резервную карту с номером %s - Отсканируйте основную карту, чтобы начать процесс резервного копирования. - Подготовьте основную карту с номером %s - Ваша карта настроена и готова к использованию. - Добавлено максимальное количество карт. Завершите процесс резервного копирования. - Активация карты - Резервная карта #%d - Нет резервных карт - Добавлена ​​одна резервная карта - Подготовьте свою карту - Добавлены две резервные карты - Пополните кошелек на любую сумму, чтобы начать пользоваться картой - Пополните кошелек более чем на %1$s %2$s, чтобы начать пользоваться картой - Купить криптовалюту - Показать адрес кошелька - Активация кошелька - Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. - Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала - Вы можете сделать резервную копию своих ключей на одной или двух других пустых картах Wallet. - Код доступа можно восстановить с помощью одной из резервных карт. - Все резервные карты являются полнофункциональными и содержат одинаковые ключи. - Вы сможете установить код доступа для защиты своих кошельков. - Резервная копия карты - Восстановление кода доступа - Идентичные карты - Код доступа - Группы - По балансу - Сортировка токенов - Список - Выбрать из галереи - Нравится наше приложение? - %1$s (%2$s) в сети %3$s - Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств. - Участвовать - Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. - Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. - Грядущие выплаты - Ваши друзья купили - Меньше - Больше - Нет грядущих выплат - - за %d кошелек - за %d кошелька - за %d кошельков - за %d кошельков - - Получите ^^%1$s^^ на ваш адрес в сети %2$s %3$s ^^спустя 30 дней^^ за каждый кошелек, который купит ваш друг - Вы - Получит - при покупке кошелька на сайте tangem.com - %s скидку - Ваш друг - Персональный код скопирован! - Ваш персональный код - Купи Tangem Wallet со скидкой!\n%s - Приведи друга в Tangem - Вы приняли - Нажимая на эту кнопку, вы принимаете - в реферальной программе - - %d кошелек - %d кошелька - %d кошельков - %d кошельков - - Сбросить карту - Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку - Я понимаю, что не смогу этой картой восстановить пароль на остальных картах этого кошелька, если я его забуду - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать данную карту для восстановления кода доступа. - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек. - У вас есть карта банка другой страны, а также вид на жительство или регистрация вне РФ? - Карты банков РФ в данный момент не принимаются - Войдите в приложение и следите за своим балансом без сканирования карты - Доступ в приложение - Использовать биометрию - Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты - Код доступа - Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков - Включите биометрическую аутентификацию - Вы хотите использовать биометрию? - Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта - Сканировать - Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. - Приготовьте свою карту - Уже содержится в введенном адресе - Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. - Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? - Причина: %1$s\nКод: %2$s - Транзакция не выполнена - Сумма - %1$s, %2$s - Адрес - Код назначения - Введите адрес - Адрес совпадает с адресом кошелька - Недопустимый Tag. Он не будет добавлен в транзакцию. - Недопустимый Memo. Он не будет добавлен в транзакцию. - Tag - Memo - Включая комиссию - Низкая - Нормальная - Приоритетная - Проверьте своё интернет соединение - Информация о комиссии сети недоступна - Из - Лимит газа - Это максимальное количество газа, которое будет потрачено на выполнение транзакции или контракта. Лимит газа предотвращает неожиданные или неограниченные расходы при выполнении транзакции. - Цена газа - Это стоимость, которую вы готовы заплатить за каждую единицу газа. Чем выше цена газа, тем быстрее ваша транзакция будет обработана. - Всё - Максимальная сумма - Комиссия не превысит - Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение. - Допустим ввод только цифр - Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии - Покрытие сетевой комиссии - Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса - Недостаточно средств - Оставить %s - Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе. - Экзистенциальный депозит - Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. - Установлена высокая комиссия - Ввиду особенности сети Tezos комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %s. - Комиссия повышена - Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению - Недопустимая сумма - Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %2$s. - Адрес получателя не активирован. \nПожалуйста, измените сумму отправки, чтобы продолжить. - Сумма отправки не может быть менее %s - Уменьшить на %s - Уменьшить до %s - Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции - Возможны задержки по транзакции - Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. - Лимит транзакции - Опционально - Пожалуйста, совместите свой QR-код с квадратом, чтобы отсканировать его. Убедитесь, что вы сканируете адрес в сети %s. - Последние - Получатель - Неверный адрес - Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов - Отправить - Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. - Мои кошельки - Способ измерения комиссии за биткоин-транзакцию. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый виртуальный байт в транзакции. Чем выше число, тем быстрее будет обработана транзакция майнерами. - Сатоши / вбайт - Отправка - Нажмите на любое поле, чтобы изменить его - Отправка %s - Вы отправляете %1$s, включая комиссию сети %2$s - Отправка %s - Всего - %1$s и %2$s будет отправлено - ≈ %1$s (вкл. комиссию: %2$s) - %s будет отправлено - Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время - Неверный адрес - Транзакция отправлена - Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. - Революционный аппаратный кошелек - До трех карт с одним кошельком - Все ключи в безопасности - Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте - Тысячи криптовалют - Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. - Кошелек для каждого - Встречайте Tangem - Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах - Поддержка Web 3.0 - В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя - В сумму включена комиссия провайдера сервиса. - Комиссии - Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен. - Подтвердить - Вы отправляете - Дать разрешение - Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму. - Недостаточно средств - Сумма отправки будет уменьшена для покрытия выбранного уровня комиссии - Подтвердить - Текущая транзакция - Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена. - Дать разрешение - Укажите лимит доступа к выбранному токену - Количество %s - Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s - Безлимитно - В процессе - Обменять - Вы получите - Выберите токен - не доступен - Балансы скрыты - Балансы показаны - Отменить - Выбранная операция в данный момент недоступна. Попробуйте позже. - В данный момент покупка монеты %s недоступна. Следите за нашими обновлениями. - У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. - У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. - В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. - Продажа средств станет доступной после завершения транзакции(-ий) в сети %s - Отправка средств станет доступной после завершения транзакции(-ий) в сети %s - В данный момент продажа %s недоступна. Следите за нашими обновлениями. - Выберите адрес - Сгенерировать XPUB - Скрыть - Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. - Скрыть %s - Скрыть токен - %1$s токен в сети %%image%% %2$s - Токен в сети %%image%% %1$s - Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. - Невозможно скрыть %s - Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля. - Обмен с Changelly, %s комиссии - Обменять - контракт: %s - У вас еще нет транзакций - Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию. - Несколько адресов - История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе. - Операция - от: %s - на: %s - Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d - Вы отсканировали не ту twin-карту. Пожалуйста, попробуйте отсканировать другую - Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька. - Один кошелек. Две карты. - Сканировать карту #%s - Создание кошелька - Отсканируйте twin-карту #%s - Подготовка карты - Tangem Twin - Это действие необратимо. У вас не будет доступа к старому кошельку. - Приложите twin-карту с номером %s и не убирайте до окончания операции - Используйте %s или отсканируйте карту, чтобы получить доступ к своему кошельку - Добавить новый кошелек - Вы уверены, что хотите удалить этот кошелек? - Произошла ошибка, пожалуйста, отсканируйте свою карту для входа - Этот кошелек уже был сохранен, вы можете добавить другой - Имя кошелька - Переименование кошелька - Разблокировать все - Разблокировать все с %s - Блокчейн недоступен. Попробуйте позже. - Отсканируйте карту - Запрос на подпись сообщения.\n\n%s - Dapp %1$s, запрос на\nподпись транзакции с BNB.\n\n%2$s - Торговый ордер на %1$s\nЦена: %2$s\nСумма к получению: %3$s\nСумма к оплате: %4$s - Детали транзакции:\nОт: %1$s\nК: %2$s\nСумма: %3$s - Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код - Запрос на создание транзакции для %1$s\n%2$s\n\nСумма: %3$s\nКомиссия: %4$s\nВсего: %5$s\nБаланс: %6$s - Невозможно отправить транзакцию. Недостаточно средств. - Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. - Не все токены добавлены в ваш список. Пожалуйста, добавьте их в начале, а потом попробуйте снова. Недостающие токены: \n - Не удалось подписать сообщение.\nПожалуйста, попробуйте еще раз - Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже. - Запрос на подключение через WalletConnect содержит неподдерживаемые блокчеины. Неподдерживаемые блокчеины:\n - Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. - Произошла непредвиденная ошибка. Сообщение ошибки: %s Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. - Неверная карта выбрана в приложении Tangem - Не удалось создать транзакцию из данных Dapp. Код: %s - Произошла непредвиденная ошибка. Код ошибки: %d Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. - Нет открытых сессий WalletConnect - Упс. Нет сессий. - Не удалось создать пару WalletConnect: %1$s - Вставить из буфера обмена - Сообщение для %1$s:\n%2$s - Запрос на открытие сессии для\n%1$s\n\nСЕТЬ: %2$s\n\nURL: %3$s - Операция не может быть завершена.\n\nВы уже установили сеанс WalletConnect с этими параметрами. - Сканировать новый код - Эту карту нельзя использовать с WalletConnect. - Сеть не поддерживается. Пожалуйста, выберите другую сеть. - Выберите сеть - Сессии WalletConnect - Подключение к dApps - WalletConnect - Рыночная цена %s - за 24 часа - Сеть %s - Адрес скопирован в буфер обмена - Нет соединения с интернетом - Tangem - Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку - Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян. - Ошибка активации - Можно лучше - Нравится - Понятно! - Очень круто! - Обновить - Вы находитесь в режиме демо - Демо режим включен - Отсканированная вами карта является картой разработчика. Не используйте ее для создания своего кошелька. - Не для пользователя! - Cеть %1$s использует концепцию экзистенциального депозита. Если баланс вашего счета будет ниже %2$s, то он будет деактивирован, а средства на счете уничтожены. - Для работы с сетью необходим депозит - Обмен будет доступен после завершения %s транзакции - У вас есть активная транзакция - Разрешение обмена в процессе и будет скоро завершено - Разрешение в процессе - Минимальная сумма обмена - %1$s. Пожалуйста, убедитесь, что остаток после обмена также не будет меньше %2$s. - У вас в списке нет монет доступных для обмена с %s - Нет доступных для обмена токенов - Чтобы совершить транзакцию, вам необходимо внести немного %1$s %2$s - Невозможно покрыть комиссию %s - Сумма получения не может быть менее %s - Cервис временно недоступен - Сумма для обмена должна быть не более %s - Сумма для обмена должна быть не менее %s - Пожалуйста, измените сумму для обмена - Возможно, данная карта - образец или подделка - Ошибка проверки подлинности - Ассоциировать - Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%1$s %2$s - Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять - Ассоциируете свой токен - Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена - На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. - Малое количество подписей - Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. - - Используйте вашу карту, чтобы получить адрес для %d сети - Используйте вашу карту, чтобы получить адреса для %d сетей - Используйте вашу карту, чтобы получить адреса для %d сетей - Используйте вашу карту, чтобы получить адреса для %d сетей - - Некоторые адреса отсутствуют - В данный момент сеть недоступна. Пожалуйста, попробуйте позже. - Сеть недоступна - Пополните ваш кошелек - Ваш кошелек не имеет резервной копии. Проведите эту процедуру сейчас, чтобы защитить ваши активы. - Резервная копия отсутствует - Эта карта ранее использовалась для подписи транзакций. Если она получена от ненадежного источника, рассмотрите возможность вывода своих средств. Если это ваша карта, дополнительных действий не требуется. - Карта уже подписывала транзакции - Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше - Нравится Tangem? - Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его - Необходима плата за аренду сети - %1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети. - Недостаточно %1$s для оплаты комиссии сети - Транзакция в обработке - Сеть Солана испытывает высокую нагрузку. Если Ваша транзакция не прошла в течение 2 минут, повторите её отправку. - Оповещение сети Солана - Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату. - Некоторые сети в настоящее время недоступны. Пожалуйста, повторите попытку позже. - Некоторые сети недоступны - Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки. - Только для целей тестирования - Отказаться - Вы не закончили резервное копирование. Хотите продолжить? - Да, возобновить - Отказаться - Если сейчас отказаться, то придётся сбрасывать карты до заводских настроек, чтобы начать заново - Возобновить резервное копирование - Это необратимое действие - Войти с %s - Сканировать карту - Используйте %s или отсканируйте карту для входа в приложение - C возвращением! + Добавить токен + Валюты + Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств. + Обратиться в поддержку + Попробовать снова + Эта функция недоступна в демонстрационном режиме + Причина: %s + Не могу отправить транзакцию + Выбранный кошелёк не поддерживает сеть %1$s + Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. + Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. + Спасибо за ваш отзыв. Мы ответим в кратчайшие сроки. + Ваши предложения отправлены + Пожалуйста, попробуйте приложить карту в точности, как показано на анимации, или запросите поддержку. + У вас возникли трудности со сканированием карты? + Эта карта не предназначена для работы с этим приложением + Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. + Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem + Включите биометрическую аутентификацию + Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. + При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены из приложения. + Сохранение кода доступа + Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. + Cохранение кошелька + Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. + Тёмная + Светлая + Как в системе + При выборе настройки как в системе приложение будет использовать тему в соответствии с настройками вашего устройства + Системная + Тема + Настройки приложения + Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" + Больше не показывать + Понятно + Балансы скрыты + Пожалуйста, отсканируйте карту + Пожалуйста, попробуйте снова через 30 секунд или отсканируйте карту + Слишком много попыток + Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона. + Начать резервное копирование + + %d карта + %d карты + %d карт + %d карт + + Отключите эту опцию, если не хотите, чтобы эта карта использовалась для сброса кодов доступа на другие карты этого кошелька. Обратите внимание, сброс кода также не будет доступен на этой карте. + Использовать эту карту для сброса кода доступа на других картах в этом кошельке + Восстановление кода доступа + Сбросить + Вы уверены, что хотите это сделать? + Смена кода доступа + Код доступа будет изменен только на данной карте + Заводские настройки + Тип безопасности + Настройки карты + Помимо сетевой комиссий, сеть Cardano взимает %1$s ADA при транзакции с токеном %2$s + Требования к транзакции Cardano + Чтобы совершить транзакцию %1$s, внесите некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA (рекомендуется 5 ADA) + Недостаточно ADA для транзакции + Вы должны поддерживать некоторое количество ADA, поскольку у вас на балансе есть токены в сети Cardano + Недостаточно ADA + Принять + Доступ запрещен + Применить + Одобрение + Внимание + Баланс: %s + Баланс + биометрическую аутентификацию + биометрией + Купить + Перейти на %1$s + Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. + Отмена + Закрыть + Продолжить + Копировать + Скопировать адрес + Создать + Удалить + Отключено + Отключить + Готово + Включить + Включено + Ошибка + Обозреватель + Посмотреть историю транзакций + Обозреватель + Комиссия + Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s + Свое + Быстро + По рынку + Медленно + Скорость и комиссия + Получить адреса + К провайдеру + Импортировать + Заблокирован + Основная сеть + Сетевая комиссия + Далее + Нет + Нет адреса + OK + Основная карта + Парольная фраза + Вставить + Подробнее + Получить + Отклонить + Перезагрузить + Переименовать + Повторить + Сохранить изменения + Искать + Поиск токенов + Seed-фраза + Выберите действие + Продать + Отправить + Сервер недоступен, повторите попытку позднее + Поделиться + Подписать + Подписать и отправить + Начать + Отправить + Успешно + Поддержка + Обмен + условия участия + Ошибка транзакции + Транзакции + Перевод + Я понял + Произошла ошибка. Пожалуйста, попробуйте снова. + Недоступно + Предупреждение + Да + Адрес контракта скопирован! + Доступные сети + Добавить токен + Адрес контракта + Адрес контракта некорректен + Пожалуйста, выберите сеть + Десятичное число должно быть действительным целым числом, до %li + Своя деривация + Например m/00\'/0000\'/0\'/0/0 + Введите свою деривацию + Знаков после запятой + Путь деривации + По умолчанию + Деривация по BIP44 + Введенный путь деривации некорректен + Например, USD Coin + Название токена + Не выбрано + Сеть + Сеть + Вы можете добавить токен в ручную, если он не поддерживается Tangem + Например, USDC + Символ + Символ токена + Этот токен/сеть уже находится в вашем списке + Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить + Остерегайтесь мошеннических токенов, они могут ничего не стоить + Токены могут быть созданы кем угодно + Чат + Код доступа + Перед сканированием карты вам нужно будет ввести правильный код доступа. + Задержка сканирования + Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. + Пароль + Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. + Реферальная программа + Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы + %s хэшей + Номер карты + Обратиться в поддержку + Добавить еще карты + Валюта приложения + Скрывать балансы жестом переворота + Эмитент + Подписано + Если вы забудете код, то потеряете доступ к своим средствам. Восстановление кода невозможно. + Подробности + Проверьте подключение с интернетом или переключитесь на другую сеть + Условия использования + Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. + Мои токены + У вас нет добавленных токенов. Добавьте токены для обмена + Недоступен для обмена с %s + Предоставлено + Статус + Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами + Выберите провайдера + Произошла ошибка. Код: %s + К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) + Выбранный провайдер недоступен для обмена. Попробуйте позже. (Код: %s) + В данный момент обмен невозможен. Попробуйте позже. (Код: %s) + Курс обмена + Обмен через %s + Чтобы вернуть ваши деньги, посетите сайт провайдера + Операция не выполнена провайдером + Посетите сайт провайдера для проверки + Провайдер запрашивает прохождение верификации + Отменен + Подтверждено + Подтверждение + Подтверждение... + Обменяно + Обмен + Обмен... + Неудачно + Депозит получен + Ожидание депозита + Ожидаем пополнения... + Возвращено + Отправляем + Отправка средств... + Отправлено + Данные провайдера. Сумма к получению может измениться в зависимости от рыночных условий. + Статус обмена + Требуется верификация + Список токенов в вашем кошельке + Получение наилучших курсов... + Плавающая ставка + Пользуясь сервисом, вы соглашаетесь с %s + Пользуясь сервисом, вы соглашаетесь с %1$s и %2$s + Больше провайдеров на подходе.\nСледите за обновлениями! + Политикой конфиденциальности + Провайдер + Лучший курс + Доступно до %s + Доступно с %s + Недоступно для этой пары + Требуется разрешение + Условиями использования + Токены не найдены. Пожалуйста, попробуйте другой запрос + ID транзакции скопирован + Информация ниже не является обязательной. Вы можете стереть её, если хотите. + Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. + Скажите, пожалуйста, какая у вас карта? + Привет, команда поддержки, + Пожалуйста, расскажите нам больше о вашей проблеме. Каждая маленькая деталь может помочь. + Мои предложения + Не могу отсканировать карту + Обращение в поддержку + Обращение в поддержку Tangem + Не могу отправить транзакцию + Купить + Сканировать + Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции + Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции + Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции + Приложите, чтобы отсканировать + Приложите, чтобы подписать + Приложите карту + Вы обновили данные биометрии, отсканируйте свою карту для входа + Чтобы начать отслеживать свои криптоактивы и транзакции, добавьте токены + Управление токенами + Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту + Отсканируйте карту + Обменивайте свои токены с %1$s комиссии провайдера через Changelly с %2$s по %3$s февраля. + Обмен с Changelly, %s комиссии + Токены + Добавить + Изменить + Рыночная капитализация + Основная сеть + Не основной или основной блокчейн, на котором размещен токен + Не основные сети + Выберите сети + Кошелек + Не удалось найти этот токен, вы можете добавить его вручную. + + %1$d из %2$d кошелька + %1$d из %2$d кошельков + %1$d из %2$d кошельков + %1$d из %2$d кошельков + + например Bitcoin + Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление. + Голосовать + Выберите кошелек + Кошелёк не поддерживает более одной сети + Вам необходимо установить единый код доступа для защиты всех ваших карт + Защита + Позже вы сможете установить индивидуальный код доступа для каждой карты + Персонализация + Код доступа можно восстановить с помощью привязанной карты. Не храните все карты в одном месте. + Восстановление + Выберите любое слово, фразу или число в качестве кода доступа + Создайте код доступа + Введите код доступа еще раз, чтобы избежать ошибки + Повторно введите код доступа + Код доступа должен состоять не менее чем из 4 символов. + Введенные коды доступа не совпадают + Необходимо повторить операцию, при этом карта будет сброшена к заводским настройкам + Ошибка активации + Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? + Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. + Парольная фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. + Добавить резервную карту + Сканировать карту #%d + Создать резервную копию + Сканировать основную карту + Перейти к моему кошельку + Завершение бэкапа + Получить криптовалюту + Сканировать основную карту + Пропустить + Как это работает? + Давайте сгенерируем все ключи на вашей карте и создадим безопасный кошелек + Создать кошелек + Создать кошелек + Другие опции + Ваши ключи будут надежно сгенерированы внутри карты. Никакой seed-фразы, а это значит, что никто не может экспортировать или украсть ее. + Cоздавайте ключи приватно + Ваша карта активирована и готова к использованию + Успешно! + В этом случае вам будет необходимо начать процесс заново. + Вы хотите выйти из процесса активации? + Подготовка + Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Если на нем есть средства, пожалуйста сначала выведите их, а затем сделайте сброс до заводских настроек и используйте как резервную. + Резервная копия + Прочитать о seed-фразе + + + Запишите эти %d слова в порядке, указанном ниже, и сохраните их в надежном месте. + Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. + Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. + + Ваша seed-фраза + + + %d слова + %d слов + %d слов + + Чтобы импортировать кошелек, введите seed-фразу в поле ниже + Создать seed-фразу + Импорт кошелька + Seed-фраза — это набор слов, который дает возможность восстановить кошелек. В отличие от ключей, сгенерированных картой, seed-фраза не защищена и может быть скопирована и украдена. Используйте этот вариант на свой страх и риск. + Использовать seed-фразу + Неверная seed-фраза. Пожалуйста, проверьте порядок слов. + Неверная seed-фраза. Пожалуйста, проверьте орфографию. + Устаревший + Чтобы проверить, правильно ли вы записали seed-фразу, введите 2-е, 7-е и 11-е слова + Итак, проверим + Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. + Вы можете добавить еще одну карту или завершить процесс резервного копирования + Подготовьте резервную карту с номером %s + Отсканируйте основную карту, чтобы начать процесс резервного копирования. + Подготовьте основную карту с номером %s + Ваша карта настроена и готова к использованию. + Добавлено максимальное количество карт. Завершите процесс резервного копирования. + Активация карты + Резервная карта #%d + Нет резервных карт + Добавлена ​​одна резервная карта + Подготовьте свою карту + Добавлены две резервные карты + Пополните кошелек на любую сумму, чтобы начать пользоваться картой + Пополните кошелек более чем на %1$s %2$s, чтобы начать пользоваться картой + Купить криптовалюту + Показать адрес кошелька + Активация кошелька + Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. + Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала + Вы можете сделать резервную копию своих ключей на одной или двух других пустых картах Wallet. + Код доступа можно восстановить с помощью одной из резервных карт. + Все резервные карты являются полнофункциональными и содержат одинаковые ключи. + Вы сможете установить код доступа для защиты своих кошельков. + Резервная копия карты + Восстановление кода доступа + Идентичные карты + Код доступа + Группы + По балансу + Сортировка токенов + Список + Выбрать из галереи + Нравится наше приложение? + %1$s (%2$s) в сети %3$s + Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств. + Участвовать + Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. + Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. + Грядущие выплаты + Ваши друзья купили + Меньше + Больше + Нет грядущих выплат + + за %d кошелек + за %d кошелька + за %d кошельков + за %d кошельков + + Получите ^^%1$s^^ на ваш адрес в сети %2$s %3$s ^^спустя 30 дней^^ за каждый кошелек, который купит ваш друг + Вы + Получит + при покупке кошелька на сайте tangem.com + %s скидку + Ваш друг + Персональный код скопирован! + Ваш персональный код + Купи Tangem Wallet со скидкой!\n%s + Приведи друга в Tangem + Вы приняли + Нажимая на эту кнопку, вы принимаете + в реферальной программе + + %d кошелек + %d кошелька + %d кошельков + %d кошельков + + Сбросить карту + Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку + Я понимаю, что не смогу этой картой восстановить пароль на остальных картах этого кошелька, если я его забуду + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать данную карту для восстановления кода доступа. + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек. + У вас есть карта банка другой страны, а также вид на жительство или регистрация вне РФ? + Карты банков РФ в данный момент не принимаются + Войдите в приложение и следите за своим балансом без сканирования карты + Доступ в приложение + Использовать биометрию + Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты + Код доступа + Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков + Включите биометрическую аутентификацию + Вы хотите использовать биометрию? + Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта + Сканировать + Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. + Приготовьте свою карту + Уже содержится в введенном адресе + Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. + Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? + Причина: %1$s\nКод: %2$s + Транзакция не выполнена + Сумма + %1$s, %2$s + Адрес + Код назначения + Введите адрес + Адрес совпадает с адресом кошелька + Недопустимый Tag. Он не будет добавлен в транзакцию. + Недопустимый Memo. Он не будет добавлен в транзакцию. + Tag + Memo + Включая комиссию + Низкая + Нормальная + Приоритетная + Проверьте своё интернет соединение + Информация о комиссии сети недоступна + Из + Лимит газа + Это максимальное количество газа, которое будет потрачено на выполнение транзакции или контракта. Лимит газа предотвращает неожиданные или неограниченные расходы при выполнении транзакции. + Цена газа + Это стоимость, которую вы готовы заплатить за каждую единицу газа. Чем выше цена газа, тем быстрее ваша транзакция будет обработана. + Всё + Максимальная сумма + Комиссия не превысит + Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение. + Допустим ввод только цифр + Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии + Покрытие сетевой комиссии + Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса + Недостаточно средств + Оставить %s + Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе. + Экзистенциальный депозит + Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. + Установлена высокая комиссия + Ввиду особенности сети Tezos комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %s. + Комиссия повышена + Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению + Недопустимая сумма + Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %2$s. + Адрес получателя не активирован. \nПожалуйста, измените сумму отправки, чтобы продолжить. + Сумма отправки не может быть менее %s + Уменьшить на %s + Уменьшить до %s + Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции + Возможны задержки по транзакции + Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. + Лимит транзакции + Опционально + Пожалуйста, совместите свой QR-код с квадратом, чтобы отсканировать его. Убедитесь, что вы сканируете адрес в сети %s. + Последние + Получатель + Неверный адрес + Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов + Отправить + Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. + Мои кошельки + Способ измерения комиссии за биткоин-транзакцию. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый виртуальный байт в транзакции. Чем выше число, тем быстрее будет обработана транзакция майнерами. + Сатоши / вбайт + Отправка + Нажмите на любое поле, чтобы изменить его + Отправка %s + Вы отправляете %1$s, включая комиссию сети %2$s + Отправка %s + Всего + %1$s и %2$s будет отправлено + ≈ %1$s (вкл. комиссию: %2$s) + %s будет отправлено + Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время + Неверный адрес + Транзакция отправлена + Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. + Революционный аппаратный кошелек + До трех карт с одним кошельком + Все ключи в безопасности + Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте + Тысячи криптовалют + Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. + Кошелек для каждого + Встречайте Tangem + Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах + Поддержка Web 3.0 + В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя + В сумму включена комиссия провайдера сервиса. + Комиссии + Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен. + Подтвердить + Вы отправляете + Дать разрешение + Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму. + Недостаточно средств + Сумма отправки будет уменьшена для покрытия выбранного уровня комиссии + Подтвердить + Текущая транзакция + Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена. + Дать разрешение + Укажите лимит доступа к выбранному токену + Количество %s + Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s + Безлимитно + В процессе + Обменять + Вы получите + Выберите токен + не доступен + Балансы скрыты + Балансы показаны + Отменить + Выбранная операция в данный момент недоступна. Попробуйте позже. + В данный момент покупка монеты %s недоступна. Следите за нашими обновлениями. + У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. + У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. + В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. + Продажа средств станет доступной после завершения транзакции(-ий) в сети %s + Отправка средств станет доступной после завершения транзакции(-ий) в сети %s + В данный момент продажа %s недоступна. Следите за нашими обновлениями. + Выберите адрес + Сгенерировать XPUB + Скрыть + Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. + Скрыть %s + Скрыть токен + %1$s токен в сети %%image%% %2$s + Токен в сети %%image%% %1$s + Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. + Невозможно скрыть %s + Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля. + Обмен с Changelly, %s комиссии + Обменять + контракт: %s + У вас еще нет транзакций + Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию. + Несколько адресов + История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе. + Операция + от: %s + на: %s + Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d + Вы отсканировали не ту twin-карту. Пожалуйста, попробуйте отсканировать другую + Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька. + Один кошелек. Две карты. + Сканировать карту #%s + Создание кошелька + Отсканируйте twin-карту #%s + Подготовка карты + Tangem Twin + Это действие необратимо. У вас не будет доступа к старому кошельку. + Приложите twin-карту с номером %s и не убирайте до окончания операции + Используйте %s или отсканируйте карту, чтобы получить доступ к своему кошельку + Добавить новый кошелек + Вы уверены, что хотите удалить этот кошелек? + Произошла ошибка, пожалуйста, отсканируйте свою карту для входа + Этот кошелек уже был сохранен, вы можете добавить другой + Имя кошелька + Переименование кошелька + Разблокировать все + Разблокировать все с %s + Блокчейн недоступен. Попробуйте позже. + Отсканируйте карту + Запрос на подпись сообщения.\n\n%s + Dapp %1$s, запрос на\nподпись транзакции с BNB.\n\n%2$s + Торговый ордер на %1$s\nЦена: %2$s\nСумма к получению: %3$s\nСумма к оплате: %4$s + Детали транзакции:\nОт: %1$s\nК: %2$s\nСумма: %3$s + Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код + Запрос на создание транзакции для %1$s\n%2$s\n\nСумма: %3$s\nКомиссия: %4$s\nВсего: %5$s\nБаланс: %6$s + Невозможно отправить транзакцию. Недостаточно средств. + Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. + Не все токены добавлены в ваш список. Пожалуйста, добавьте их в начале, а потом попробуйте снова. Недостающие токены: \n + Не удалось подписать сообщение.\nПожалуйста, попробуйте еще раз + Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже. + Запрос на подключение через WalletConnect содержит неподдерживаемые блокчеины. Неподдерживаемые блокчеины:\n + Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. + Произошла непредвиденная ошибка. Сообщение ошибки: %s Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. + Неверная карта выбрана в приложении Tangem + Не удалось создать транзакцию из данных Dapp. Код: %s + Произошла непредвиденная ошибка. Код ошибки: %d Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. + Нет открытых сессий WalletConnect + Упс. Нет сессий. + Не удалось создать пару WalletConnect: %1$s + Вставить из буфера обмена + Сообщение для %1$s:\n%2$s + Запрос на открытие сессии для\n%1$s\n\nСЕТЬ: %2$s\n\nURL: %3$s + Операция не может быть завершена.\n\nВы уже установили сеанс WalletConnect с этими параметрами. + Сканировать новый код + Эту карту нельзя использовать с WalletConnect. + Сеть не поддерживается. Пожалуйста, выберите другую сеть. + Выберите сеть + Сессии WalletConnect + Подключение к dApps + WalletConnect + Рыночная цена %s + за 24 часа + Сеть %s + Адрес скопирован в буфер обмена + Нет соединения с интернетом + Tangem + Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку + Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян. + Ошибка активации + Можно лучше + Нравится + Понятно! + Очень круто! + Обновить + Вы находитесь в режиме демо + Демо режим включен + Отсканированная вами карта является картой разработчика. Не используйте ее для создания своего кошелька. + Не для пользователя! + Cеть %1$s использует концепцию экзистенциального депозита. Если баланс вашего счета будет ниже %2$s, то он будет деактивирован, а средства на счете уничтожены. + Для работы с сетью необходим депозит + Обмен будет доступен после завершения %s транзакции + У вас есть активная транзакция + Разрешение обмена в процессе и будет скоро завершено + Разрешение в процессе + Минимальная сумма обмена - %1$s. Пожалуйста, убедитесь, что остаток после обмена также не будет меньше %2$s. + У вас в списке нет монет доступных для обмена с %s + Нет доступных для обмена токенов + Чтобы совершить транзакцию, вам необходимо внести немного %1$s %2$s + Невозможно покрыть комиссию %s + Сумма получения не может быть менее %s + Cервис временно недоступен + Сумма для обмена должна быть не более %s + Сумма для обмена должна быть не менее %s + Пожалуйста, измените сумму для обмена + Возможно, данная карта - образец или подделка + Ошибка проверки подлинности + Ассоциировать + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%1$s %2$s + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять + Ассоциируете свой токен + Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена + На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. + Малое количество подписей + Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. + + Используйте вашу карту, чтобы получить адрес для %d сети + Используйте вашу карту, чтобы получить адреса для %d сетей + Используйте вашу карту, чтобы получить адреса для %d сетей + Используйте вашу карту, чтобы получить адреса для %d сетей + + Некоторые адреса отсутствуют + В данный момент сеть недоступна. Пожалуйста, попробуйте позже. + Сеть недоступна + Пополните ваш кошелек + Ваш кошелек не имеет резервной копии. Проведите эту процедуру сейчас, чтобы защитить ваши активы. + Резервная копия отсутствует + Эта карта ранее использовалась для подписи транзакций. Если она получена от ненадежного источника, рассмотрите возможность вывода своих средств. Если это ваша карта, дополнительных действий не требуется. + Карта уже подписывала транзакции + Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше + Нравится Tangem? + Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его + Необходима плата за аренду сети + %1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети. + Недостаточно %1$s для оплаты комиссии сети + Транзакция в обработке + Сеть Солана испытывает высокую нагрузку. Если Ваша транзакция не прошла в течение 2 минут, повторите её отправку. + Оповещение сети Солана + Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату. + Некоторые сети в настоящее время недоступны. Пожалуйста, повторите попытку позже. + Некоторые сети недоступны + Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки. + Только для целей тестирования + Отказаться + Вы не закончили резервное копирование. Хотите продолжить? + Да, возобновить + Отказаться + Если сейчас отказаться, то придётся сбрасывать карты до заводских настроек, чтобы начать заново + Возобновить резервное копирование + Это необратимое действие + Войти с %s + Сканировать карту + Используйте %s или отсканируйте карту для входа в приложение + C возвращением! diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index ef843270c8..ea0198dfa6 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1,718 +1,720 @@ - Add custom token - Manage tokens - Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. - Request support - Try again - This feature is disabled in Demo mode - Reason: %s - Can\'t send a transaction - The selected does not support the %1$s network - To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. - Tokens in %1$s network are not supported by this card due to firmware limitation. - Thank you for your feedback. We will respond as soon as possible - Your suggestions were sent - Please try to tap the card exactly as shown in the animation or request support. - Are you having difficulty scanning your card? - This card is not designed to work with this app - Default Fee - Enable Default Fee to set transaction fees automatically and skip the Fee page when sending funds. You can always go back to this page if necessary. - Go to settings to enable biometric authentication in the Tangem App - Enable biometric authentication - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - Removing the saved card deletes all the saved wallets and their access codes from the app. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Dark - Light - System default - If system is selected, the app will auto-adjust based on your device\'s system settings - System - Theme - App Settings - To hide or show your balances, simply flip your device screen down, or switch it off in Settings - Don\'t show again - Got it - Balances are hidden - Please scan the card - Please try again in 30 seconds or scan the card - Too many attempts - You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings. - Start backup process - - %d card - %d cards - - Disable this option if you don\'t want this card to be used to reset access codes on other cards in this wallet. Please note that this will also prevent you from resetting the access code on this card. - Allows you to use this card to reset access code on other cards in this wallet - Access code recovery - Reset - Are you sure you want to do this? - Change Access Code - Access code will be changed on this card only - Reset to Factory Settings - Security Mode - Card Settings - Due to the peculiarities of the Cardano network, when transacting the %1$s token, in addition to the network commission, %2$s will be charged - To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value. (5 ADA balance recommended) - Insufficient ADA to token transfer - The Cardano network does not allow you to leave an amount on your balance below the minimum value %1$s if you have tokens. Please ensure that you have some ADA remaining. - Accept - Access denied - Apply - Approval - Attention - Balance: %s - Balance - biometric authentication - biometrics - Buy - Go to %1$s - You have not given access to your camera, please adjust your privacy settings - Cancel - Close - Continue - Copy - Copy address - Create - Delete - Disabled - Disconnect - Done - Enable - Enabled - Error - Explore - Explore transaction history - Explorer - Fee - Network fees are charges users pay to process and confirm transactions. The fee amount can be affected by network congestion, transaction size, and execution priority. %s - Custom - Fast - Market - Slow - Speed and fee - Get addresses - Go to provider - Import - Locked - Main network - Network fee - Next - No - No address - OK - Primary Card - Passphrase - Paste - Read more - Receive - Reject - Reload - Rename - Retry - Save changes - Search - Search tokens - Seed phrase - Select action - Sell - Send - The server is not available, please try again later - Share - Sign - Sign and send - Start - Submit - Success - Support - Swap - terms and conditions - Transaction failed - Transactions - Transfer - I understand - There was an error. Please try again. - Unreachable - Warning - Yes - Contract address copied! - Available networks - Add token - Contract address - Contract address is invalid - Please select the network - Decimal must be a valid integer, up to %li - Custom derivation - E. g. m/00\'/0000\'/0\'/0/0 - Enter custom derivation - Decimals - Derivation Path - Default - BIP44 coin type - The derivation path you\'ve entered is not valid - E.g. USD Coin - Name - Not selected - Network - Token network - You can manually add a token that is not natively supported by Tangem - E.g. USDC - Symbol - Token symbol - This token/network has already been added to your list - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - Be aware of adding scam tokens, they can cost nothing - Note that tokens can be created by anyone - Chat - Access code - You will have to submit the correct access code before scanning the card - Long Tap - This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. - Passcode - Before executing any command entailing a change of the card state, you will have to enter the passcode. - Referral program - Flip your device screen down to quickly hide and show balances - %s hashes - Card ID - Contact to Support - Link More Cards - App Currency - Flip-to-Hide Balances - Issuer - Signed - If you forget the code you will lose access to your funds. Code recovery is not possible. - Details - Check your internet connection or switch to a different network - Terms of Service - You have used a card from another wallet. Tap the card associated with this wallet - My tokens - You haven\'t added any tokens yet. Add tokens via Market to swap - Cannot be swapped for %s - Provided by - Status - Tangem offers token swaps via 3rd-party providers according to each provider\'s terms - Choose provider - An error occurred. Code: %s - Oops! Swapping the selected pair through the chosen provider is temporarily unavailable. Please try again later. (Code: %s) - Selected provider is unavailable at the moment. Please try again later. (Code: %s) - Swaps are unavailable at the moment. Please try again later. (Code: %s) - Estimated amount - Exchange by %s - Visit provider’s website to refund your money - Operation failed by provider - Visit provider’s website for verification - KYC verification required by provider - Canceled - Confirmed - Confirming - Confirming... - Exchanged - Exchanging - Exchanging... - Failed - Deposit received - Awaiting deposit - Awaiting deposit... - Refunded - Sending to you - Sending to you... - Sent - Provider-sourced data. Estimated amount subject to change due to market conditions. - Exchange status - Verification required - List of all tokens added to your wallet - Fetching best rates... - Floating rate - By using swap functionality, you agree with provider’s %s - By using swap functionality, you agree with provider’s %1$s and %2$s - More providers are coming soon.\nStay tuned! - Privacy Policy - Provider - Best rate - Available up to %s - Available from %s - Unavailable for this pair - Permission Required - Terms of Use - No tokens found. Please try another request - ID: %s - Transaction ID copied - The following information is optional. You can erase it if you don\'t want to share it. - Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have - Hi support team, - Please tell us more about your issue. Every small detail can help. - My suggestions - Can\'t scan a card - Feedback - Tangem feedback - Can\'t send a transaction - Order card - Scan card - To change the access code tap the card as shown above and do not remove until the end of the operation - To change the passcode tap the card as shown above and do not remove until the end of the operation - To create the wallet tap the card as shown above and do not remove until the end of the operation - Tap to scan - Tap to sign - Tap the card - You have updated biometrics, scan your card to enter - To begin tracking your crypto assets and transactions, add tokens - Manage tokens - To access all the networks you need to scan the card - Scan your card - Enjoy %1$s service fees on swaps via Changelly from February %2$s-%3$s - Swap with Changelly, %s fees - Tokens - Add - Edit - Coin market cap - Blockchain the cryptocurrency was initially created - Native network - Using non-native networks for tokens enables cross-blockchain interoperability, allowing assets to be utilized in diverse decentralized applications and smart contracts across platforms. However, this often involves a custodian or smart contract to hold the original asset securely, introducing centralization and counterparty risk. - Not original or primary blockchain the token is hosted - Non-native networks - Choose networks - Wallet - Couldn’t find this token, you can add it manually - - %1$d of %2$d wallet - %1$d of %2$d wallets - - e.g. BTC I trust, hodl I must - The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it. - Upvote - Choose wallet - The wallet doesn\'t support more than one network - You have to set up a single access code to protect all your cards - Protect - You can set up an individual access code on each card later - Personalize - The access code can be restored with a linked card. Don’t keep all cards at one place. - Restore - Choose any word, phrase, or number you want as your access code - Create Access Code - Enter your access code one more time to avoid a mistake - Re-enter your Access Code - Access code must be at least 4 characters long - Entered access code didn\'t match the initial access code - Please repeat the operation. The card will be reset to factory settings. - Activation error - You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? - The backup process is partly complete. You can\'t exit it now. - The passphrase is an advanced security feature that crypto wallets use. It adds an extra word or phrase of your own choosing to your already existing recovery phrase to unlock a brand-new set of addresses. - Add a backup card - Scan the card #%d - Backup now - Scan the primary card - Continue to my wallet - Finalize the backup - Receive crypto - Scan primary card - Skip for later - How does it work? - Let\'s generate all the keys on your card and create a secure wallet - Create wallet - Create a wallet - Other options - Your keys will be securely generated inside the card. There is no seed phrase, which means nobody can export or steal it. - Generate keys privately - Your card is activated and ready to be used - Success! - In this case, you will need to start from the beginning. - Do you want to exit the activation process? - Getting started - Another wallet has already been created on the card you\'re trying to add. If you have funds in this wallet, please withdraw it and then reset this card and add it as a backup. - Creating a backup - Read more about seed phrase - - - Write these %d words down in the order given below and store them in a safe and secret place. - - Your seed phrase - - - %d words - - To import your wallet, enter your seed phrase in the field below - Generate seed phrase - Import wallet - A seed phrase is a series of words that allows you to recover your wallet. Unlike the keys generated by the card, seed phrases are unprotected and can be copied and stolen. Use this option at your own risk. - Use seed phrase - Invalid seed phrase. Please check the word order. - Invalid seed phrase. Please check your spelling. - Legacy - To check whether you’ve written down your seed phrase correctly, please enter the 2nd, 7th and 11th words - So, let’s check - To start the backup process add up to two backup cards. - You can add one more card or finalize the backup process - Prepare the backup card with number %s - Scan the primary card to start the backup process. - Prepare the primary card with number %s - Your wallet card is configured and ready for use. - Max number of cards added. Finalize the backup process. - Activating card - Backup card #%d - No backup cards - One backup card added - Prepare your card - Two backup cards added - To get started, simply top up the wallet with any amount - To get started, simply top up the wallet with more than %1$s %2$s - Buy crypto - Show the wallet\'s address - Activate a wallet - The twinning process is partly complete. You can\'t exit it now. - If the process of creating the wallet gets interrupted in any way, you\'ll have to start over - You can backup your keys up to two other blank Tangem Wallet cards. - Access code can be restored with one of backup cards. - All the backup cards can be used as full-functional with the identical keys. - You will be able to set an access code to protect your wallets. - Backup wallet - Access code restore - Identical cards - Access code - Group - By balance - Organize tokens - Ungroup - Select from the gallery - Settings - You have not given access to your camera - Camera access denied - Enjoying our app? - %1$s (%2$s) on %3$s network - Send only %s to this address. Sending any other currency will result in its irreversible loss. - Participate - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Error code: %s. Please try again later. - Upcoming payments - Your friends bought - Less - More - No upcoming payments - - for %d wallet - for %d wallets - - Will get ^^%1$s^^ for each wallet bought by your friend on your %2$s network address %3$s ^^30 days after^^ that - You - Will get a - when buying a wallet on tangem.com - %s discount - Your friend - Personal code copied! - Your personal code - Buy Tangem Wallet with discount!\n%s - Refer your friends to Tangem - You\'ve accepted - By tapping this button you accept - of the referral program - - %d wallet - %d wallets - - Reset the Card - I understand that after performing this action, I will no longer have access to the current wallet - I realize that I can\'t use this card to recover my access code on the other cards of the current wallet - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. - Do you have a bank card from another country and a residence permit or registration outside the Russian Federation? - Russian bank cards are not currently accepted - Log into the app and check your balance without scanning the card - Access the app - Allow to use biometrics - Biometrics will be requested instead of the access code for interactions with your wallet - Access code - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable biometric authorization - Would you like to use biometrics? - Note that making a transaction with your funds will still require your card - Scan Card - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Get your card ready! - Already included in the entered address - The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. - You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? - Reason: %1$s\nCode: %2$s - The transaction is not completed - Amount - Base fee - Represents the part of the transaction fee that goes to the miner - %1$s, %2$s - Address - Destination Tag - Enter address - Address is the same as wallet address - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Include fee - Low - Normal - Priority - Check your network connection - Network fee info unreachable - From - Gas limit - This is the maximum amount of gas that will be spent to complete a transaction or contract. A gas limit prevents unexpected or unlimited charges when executing a transaction. - Gas price - This is the cost you are willing to pay for each unit of gas. The higher the gas price, the faster your transaction will be processed. - Max - Maximum amount - Max fee - The fee that will be charged for your transaction. You can set your own value. - Numbers only for Destination Tag - Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level - Network fee coverage - Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance - Total exceeds balance - Leave %s - The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance. - Existential deposit - The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. - Custom fee is high - Due to the peculiarities of the Tezos network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %s. - The fee is higher - The included commission exceeds the transfer amount, leading to a negative value - Invalid amount - The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s. - Target account is not created. Please change the amount to send. - The amount to send must be at least %s - Reduce by %s - Reduce to %s - Kindly be aware that your transaction may experience delays under specific fee settings - Transaction delays are possible - Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount. - Transaction limitation - Optional - Priority fee - Represents the minimum gasUsed multiplier required for a transaction to be included in a block. This is the part of the transaction fee that is burnt. - Please align your QR code with the square to scan it. Ensure you scan %s network address. - Recent - Recipient - Not a valid address - Ensure that you are sending funds to an %s wallet address. Errors may result in the loss of your tokens - Send to - A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds - My wallets - A way of measuring Bitcoin transaction fees. It indicates the number of the smallest Bitcoin unit (Satoshi) for each virtual byte in a transaction. The higher the number, the faster the transaction will be processed by miners. - Satoshi / vByte - Sending... - Tap any field to change it - Send %s - You are sending %1$s including a network fee of %2$s - Sending %s - Total - %1$s and %2$s will be sent - ≈ %1$s (inc. fee: %2$s) - %s will be sent - Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while - Invalid address - %1$s (%2$s) - Transaction sent - Store your crypto assets secure while keeping private keys contained in your card - Revolutionary Hardware Wallet - Up to 3 physical cards to one wallet - Ultra Secure Backup - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - Thousands of Currencies - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - The Wallet for Everyone - Meet Tangem - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - Web 3.0 Compatible - The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address. - The amount includes the service provider\'s fee. - Fees - All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval. - Approve - You swap - Give Permission - Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome. - Insufficient funds - Sending amount will be reduced to cover the selected fee level - Approve - Current transaction - The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. - Give Permission - Specify the approve limit for the selected token - Amount %s - To continue, grant 1inch smart contracts permission to use your %s - Unlimited - In progress - Swap - You receive - Choose token - not available - Balances hidden - Balances shown - Undo - This operation is currently unavailable. Please try again later. - Buying %s is not available at the moment. Please check our updates. - You do not have funds to sell. Top up your account to be able to sell funds from it. - You do not have funds to send. Top up your account to be able to send funds from it. - Swapping %s is not available at the moment. Please check our updates. - Selling funds will be available once the pending transaction(s) in network %s is complete - Sending funds will be available once the pending transaction(s) in network %s is complete - Selling %s is not available at the moment. Please check our updates. - Choose address - Generate XPUB - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Hide %s - Hide token - %1$s token in %%image%% %2$s network - Token in %%image%% %1$s network - The %1$s token is the main currency on the %2$s network and cannot be hidden as long as you have other tokens on this network in the list. - Unable to hide %s - Exchange this token for another at %1$s service fees from February %2$s-%3$s. - Swap with Changelly, %s fees - Swap now - contract: %s - You don\'t have any transactions yet - Failed to load transaction history.\nClick on reload button to update the information. - Multiple addresses - Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer. - Operation - from: %s - to: %s - You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d - You\'ve scanned wrong twin card. Please try another one - This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. - One wallet. Two cards. - Scan the card #%s - Creating wallet - Scan the #%s twin card - Preparing card - Tangem Twin - This action is irreversible. You will not have access to the old wallet. - Tap the twin card with number %s and do not remove until the end of the operation - Use %s or scan a card to have an access to your wallet - Add new wallet - Are you sure you want to delete this wallet? - An error has occurred, please scan your card to log in - This wallet has already been saved, you can add another one - Wallet name - Rename Wallet - Unlock all - Unlock all with %s - Blockchain is unreachable. Try later - Scan the card - Requesting to sign a message.\n\n%s - Dapp %1$s, requesting to\nsign BNB transaction.\n\n%2$s - Trade order for %1$s\nPrice: %2$s\nAmount to receive: %3$s\nAmount to pay: %4$s - Transaction details:\nFrom: %1$s\nTo: %2$s\nAmount: %3$s - Clipboard contain WalletConnect code. Use copied value or scan QR-code - Request to create transaction for %1$s\n%2$s\n\nAmount: %3$s\nFee: %4$s\nTotal: %5$s\nBalance: %6$s - Can\'t send transaction. Not enough funds. - Failed to establish WalletConnect session. Please, try again later. - Not all tokens were added to your list. Please add them first and try again. Missing tokens:\n - Failed to sign message.\nPlease, try again - Failed to establish WalletConnect session: timeout error. Please, try again later. - Session request contains unsupported blockchains for WalletConnect connection. Unsupported blockchains:\n - Connection with this Dapp cannot be established due to its technical implementation. - We\'ve encountered unknown error. Error message: %s. If the problem persists — feel free to contact our support - Wrong card selected in Tangem App - Failed to create transaction from Dapp data. Code: %s - We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support - No opened WalletConnect sessions - Ooops. No Sessions. - Failed to pairing WalletConnect session: %1$s - Paste from clipboard - Message for %1$s:\n%2$s - Request to start a session for\n%1$s\n\nNETWORK: %2$s\n\nURL: %3$s - The operation couldn\'t be completed.\n\nYou have already established a WalletConnect session with this parameters. - Scan new code - This card can\'t be used to establish WalletConnect session - This network is not supported. Please select another network. - Select network - WalletConnect Sessions - Connect to dApps - WalletConnect - %s Market Price - last 24h - %s network - Address was copied to clipboard - No internet connection - Tangem - Use %s or scan a card to unlock access to your wallet - Please withdraw all funds from this wallet, reset it to factory settings, and create a new one. Access to the current wallet will be lost. - Activation error - Could be better - Like it - Ok, Got it! - Really cool! - Refresh - You are currently in the Demo mode - Demo mode active - The card you scanned is a developer card. Do not use it to create your wallet. - Not for users! - %1$s network requires an Existential Deposit. If your account drops below %2$s, it will be deactivated, and any remaining funds will be destroyed. - Network requires Existential Deposit - Swap will be available after the %s transaction is complete - You have active transaction - Swap approval is underway and will be completed shortly - Approval in progress - The minimum swapping amount is %1$s. Please ensure that the remaining balance after the swap will not be less than %2$s. - You do not have any %s exchangeable coins in your list - No available tokens to swap - To make a transaction you need to deposit some %1$s %2$s - Unable to cover %s fee - The amount to receive must be at least %s - Service temporarily unavailable - The amount of tokens to be swapped must not exceed %s - The amount to swap must be at least %s - Please change the amount to swap - This card might be a production sample or counterfeit - Authenticity check failed - Associate - This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s - This token must be associated with your Hedera account before you can receive it - Associate your token - Hot enough %s. Top up your Hedera account to associate this token - Only %s signatures are left on this card. You must withdraw all of your funds. - Low signature count - Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. - - Use your card to get an address for %d network - Use your card to get an addresses for %d networks - - Some addresses are missing - The network is currently unreachable. Please try again later. - Network is unreachable - Top up your wallet - Your wallet hasn\'t been backed up. Carry out this procedure to protect your assets now. - Missing backup - This card has been previously used for transactions. If received from an untrusted source, consider withdrawing all funds. If it\'s your card, no action is required. - Card has already signed transactions - Your review keeps us motivated to make Tangem Wallet even better - Enjoying Tangem? - You must associate your token before receiving tokens - Network rent fee required - %1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. - Insufficient %1$s to cover network fee - Transaction pending - The Solana network is congested. If your transaction is not processed within 2 minutes, please repeat the transaction. - Solana Network Alert - Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free. - Some networks currently are unreachable. Please try again later. - Some networks are unreachable - This is a Testnet card. It cannot process transactions and should only be used for testing and development purposes. - For testing purposes only - Discard - You have an interrupted backup. Do you want to resume? - Yes, resume - Discard - If you will discard the backup now, then you will have to reset the cards to factory settings to start over again - Resume backup - This is an irreversible action - Log in with %s - Scan card - Use %s or scan a card to access the app - Welcome back! + Add custom token + Manage tokens + Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. + Request support + Try again + This feature is disabled in Demo mode + Reason: %s + Can\'t send a transaction + The selected does not support the %1$s network + To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. + Tokens in %1$s network are not supported by this card due to firmware limitation. + Thank you for your feedback. We will respond as soon as possible + Your suggestions were sent + Please try to tap the card exactly as shown in the animation or request support. + Are you having difficulty scanning your card? + This card is not designed to work with this app + Default Fee + Enable Default Fee to set transaction fees automatically and skip the Fee page when sending funds. You can always go back to this page if necessary. + Go to settings to enable biometric authentication in the Tangem App + Enable biometric authentication + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + Removing the saved card deletes all the saved wallets and their access codes from the app. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Dark + Light + System default + If system is selected, the app will auto-adjust based on your device\'s system settings + System + Theme + App Settings + To hide or show your balances, simply flip your device screen down, or switch it off in Settings + Don\'t show again + Got it + Balances are hidden + Please scan the card + Please try again in 30 seconds or scan the card + Too many attempts + You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings. + Start backup process + + %d card + %d cards + + Disable this option if you don\'t want this card to be used to reset access codes on other cards in this wallet. Please note that this will also prevent you from resetting the access code on this card. + Allows you to use this card to reset access code on other cards in this wallet + Access code recovery + Reset + Are you sure you want to do this? + Change Access Code + Access code will be changed on this card only + Reset to Factory Settings + Security Mode + Card Settings + In addition to network fee, the Cardano network charges %1$s ADA when transacting with the %2$s token + Cardano transaction requirements + To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value (5 ADA recommended) + Insufficient ADA for token transfer + You must maintain some ADA because you have some tokens on the Cardano blockchain + Not enough ADA + Accept + Access denied + Apply + Approval + Attention + Balance: %s + Balance + biometric authentication + biometrics + Buy + Go to %1$s + You have not given access to your camera, please adjust your privacy settings + Cancel + Close + Continue + Copy + Copy address + Create + Delete + Disabled + Disconnect + Done + Enable + Enabled + Error + Explore + Explore transaction history + Explorer + Fee + Network fees are charges users pay to process and confirm transactions. The fee amount can be affected by network congestion, transaction size, and execution priority. %s + Custom + Fast + Market + Slow + Speed and fee + Get addresses + Go to provider + Import + Locked + Main network + Network fee + Next + No + No address + OK + Primary Card + Passphrase + Paste + Read more + Receive + Reject + Reload + Rename + Retry + Save changes + Search + Search tokens + Seed phrase + Select action + Sell + Send + The server is not available, please try again later + Share + Sign + Sign and send + Start + Submit + Success + Support + Swap + terms and conditions + Transaction failed + Transactions + Transfer + I understand + There was an error. Please try again. + Unreachable + Warning + Yes + Contract address copied! + Available networks + Add token + Contract address + Contract address is invalid + Please select the network + Decimal must be a valid integer, up to %li + Custom derivation + E. g. m/00\'/0000\'/0\'/0/0 + Enter custom derivation + Decimals + Derivation Path + Default + BIP44 coin type + The derivation path you\'ve entered is not valid + E.g. USD Coin + Name + Not selected + Network + Token network + You can manually add a token that is not natively supported by Tangem + E.g. USDC + Symbol + Token symbol + This token/network has already been added to your list + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + Be aware of adding scam tokens, they can cost nothing + Note that tokens can be created by anyone + Chat + Access code + You will have to submit the correct access code before scanning the card + Long Tap + This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. + Passcode + Before executing any command entailing a change of the card state, you will have to enter the passcode. + Referral program + Flip your device screen down to quickly hide and show balances + %s hashes + Card ID + Contact to Support + Link More Cards + App Currency + Flip-to-Hide Balances + Issuer + Signed + If you forget the code you will lose access to your funds. Code recovery is not possible. + Details + Check your internet connection or switch to a different network + Terms of Service + You have used a card from another wallet. Tap the card associated with this wallet + My tokens + You haven\'t added any tokens yet. Add tokens via Market to swap + Cannot be swapped for %s + Provided by + Status + Tangem offers token swaps via 3rd-party providers according to each provider\'s terms + Choose provider + An error occurred. Code: %s + Oops! Swapping the selected pair through the chosen provider is temporarily unavailable. Please try again later. (Code: %s) + Selected provider is unavailable at the moment. Please try again later. (Code: %s) + Swaps are unavailable at the moment. Please try again later. (Code: %s) + Estimated amount + Exchange by %s + Visit provider’s website to refund your money + Operation failed by provider + Visit provider’s website for verification + KYC verification required by provider + Canceled + Confirmed + Confirming + Confirming... + Exchanged + Exchanging + Exchanging... + Failed + Deposit received + Awaiting deposit + Awaiting deposit... + Refunded + Sending to you + Sending to you... + Sent + Provider-sourced data. Estimated amount subject to change due to market conditions. + Exchange status + Verification required + List of all tokens added to your wallet + Fetching best rates... + Floating rate + By using swap functionality, you agree with provider’s %s + By using swap functionality, you agree with provider’s %1$s and %2$s + More providers are coming soon.\nStay tuned! + Privacy Policy + Provider + Best rate + Available up to %s + Available from %s + Unavailable for this pair + Permission Required + Terms of Use + No tokens found. Please try another request + ID: %s + Transaction ID copied + The following information is optional. You can erase it if you don\'t want to share it. + Tell us what functions you are missing, and we will try to help you. + Please tell us what card do you have + Hi support team, + Please tell us more about your issue. Every small detail can help. + My suggestions + Can\'t scan a card + Feedback + Tangem feedback + Can\'t send a transaction + Order card + Scan card + To change the access code tap the card as shown above and do not remove until the end of the operation + To change the passcode tap the card as shown above and do not remove until the end of the operation + To create the wallet tap the card as shown above and do not remove until the end of the operation + Tap to scan + Tap to sign + Tap the card + You have updated biometrics, scan your card to enter + To begin tracking your crypto assets and transactions, add tokens + Manage tokens + To access all the networks you need to scan the card + Scan your card + Enjoy %1$s service fees on swaps via Changelly from February %2$s-%3$s + Swap with Changelly, %s fees + Tokens + Add + Edit + Coin market cap + Blockchain the cryptocurrency was initially created + Native network + Using non-native networks for tokens enables cross-blockchain interoperability, allowing assets to be utilized in diverse decentralized applications and smart contracts across platforms. However, this often involves a custodian or smart contract to hold the original asset securely, introducing centralization and counterparty risk. + Not original or primary blockchain the token is hosted + Non-native networks + Choose networks + Wallet + Couldn’t find this token, you can add it manually + + %1$d of %2$d wallet + %1$d of %2$d wallets + + e.g. BTC I trust, hodl I must + The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it. + Upvote + Choose wallet + The wallet doesn\'t support more than one network + You have to set up a single access code to protect all your cards + Protect + You can set up an individual access code on each card later + Personalize + The access code can be restored with a linked card. Don’t keep all cards at one place. + Restore + Choose any word, phrase, or number you want as your access code + Create Access Code + Enter your access code one more time to avoid a mistake + Re-enter your Access Code + Access code must be at least 4 characters long + Entered access code didn\'t match the initial access code + Please repeat the operation. The card will be reset to factory settings. + Activation error + You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? + The backup process is partly complete. You can\'t exit it now. + The passphrase is an advanced security feature that crypto wallets use. It adds an extra word or phrase of your own choosing to your already existing recovery phrase to unlock a brand-new set of addresses. + Add a backup card + Scan the card #%d + Backup now + Scan the primary card + Continue to my wallet + Finalize the backup + Receive crypto + Scan primary card + Skip for later + How does it work? + Let\'s generate all the keys on your card and create a secure wallet + Create wallet + Create a wallet + Other options + Your keys will be securely generated inside the card. There is no seed phrase, which means nobody can export or steal it. + Generate keys privately + Your card is activated and ready to be used + Success! + In this case, you will need to start from the beginning. + Do you want to exit the activation process? + Getting started + Another wallet has already been created on the card you\'re trying to add. If you have funds in this wallet, please withdraw it and then reset this card and add it as a backup. + Creating a backup + Read more about seed phrase + + + Write these %d words down in the order given below and store them in a safe and secret place. + + Your seed phrase + + + %d words + + To import your wallet, enter your seed phrase in the field below + Generate seed phrase + Import wallet + A seed phrase is a series of words that allows you to recover your wallet. Unlike the keys generated by the card, seed phrases are unprotected and can be copied and stolen. Use this option at your own risk. + Use seed phrase + Invalid seed phrase. Please check the word order. + Invalid seed phrase. Please check your spelling. + Legacy + To check whether you’ve written down your seed phrase correctly, please enter the 2nd, 7th and 11th words + So, let’s check + To start the backup process add up to two backup cards. + You can add one more card or finalize the backup process + Prepare the backup card with number %s + Scan the primary card to start the backup process. + Prepare the primary card with number %s + Your wallet card is configured and ready for use. + Max number of cards added. Finalize the backup process. + Activating card + Backup card #%d + No backup cards + One backup card added + Prepare your card + Two backup cards added + To get started, simply top up the wallet with any amount + To get started, simply top up the wallet with more than %1$s %2$s + Buy crypto + Show the wallet\'s address + Activate a wallet + The twinning process is partly complete. You can\'t exit it now. + If the process of creating the wallet gets interrupted in any way, you\'ll have to start over + You can backup your keys up to two other blank Tangem Wallet cards. + Access code can be restored with one of backup cards. + All the backup cards can be used as full-functional with the identical keys. + You will be able to set an access code to protect your wallets. + Backup wallet + Access code restore + Identical cards + Access code + Group + By balance + Organize tokens + Ungroup + Select from the gallery + Settings + You have not given access to your camera + Camera access denied + Enjoying our app? + %1$s (%2$s) on %3$s network + Send only %s to this address. Sending any other currency will result in its irreversible loss. + Participate + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Error code: %s. Please try again later. + Upcoming payments + Your friends bought + Less + More + No upcoming payments + + for %d wallet + for %d wallets + + Will get ^^%1$s^^ for each wallet bought by your friend on your %2$s network address %3$s ^^30 days after^^ that + You + Will get a + when buying a wallet on tangem.com + %s discount + Your friend + Personal code copied! + Your personal code + Buy Tangem Wallet with discount!\n%s + Refer your friends to Tangem + You\'ve accepted + By tapping this button you accept + of the referral program + + %d wallet + %d wallets + + Reset the Card + I understand that after performing this action, I will no longer have access to the current wallet + I realize that I can\'t use this card to recover my access code on the other cards of the current wallet + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Do you have a bank card from another country and a residence permit or registration outside the Russian Federation? + Russian bank cards are not currently accepted + Log into the app and check your balance without scanning the card + Access the app + Allow to use biometrics + Biometrics will be requested instead of the access code for interactions with your wallet + Access code + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable biometric authorization + Would you like to use biometrics? + Note that making a transaction with your funds will still require your card + Scan Card + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Get your card ready! + Already included in the entered address + The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. + You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? + Reason: %1$s\nCode: %2$s + The transaction is not completed + Amount + Base fee + Represents the part of the transaction fee that goes to the miner + %1$s, %2$s + Address + Destination Tag + Enter address + Address is the same as wallet address + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Include fee + Low + Normal + Priority + Check your network connection + Network fee info unreachable + From + Gas limit + This is the maximum amount of gas that will be spent to complete a transaction or contract. A gas limit prevents unexpected or unlimited charges when executing a transaction. + Gas price + This is the cost you are willing to pay for each unit of gas. The higher the gas price, the faster your transaction will be processed. + Max + Maximum amount + Max fee + The fee that will be charged for your transaction. You can set your own value. + Numbers only for Destination Tag + Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level + Network fee coverage + Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance + Total exceeds balance + Leave %s + The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance. + Existential deposit + The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. + Custom fee is high + Due to the peculiarities of the Tezos network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %s. + The fee is higher + The included commission exceeds the transfer amount, leading to a negative value + Invalid amount + The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s. + Target account is not created. Please change the amount to send. + The amount to send must be at least %s + Reduce by %s + Reduce to %s + Kindly be aware that your transaction may experience delays under specific fee settings + Transaction delays are possible + Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount. + Transaction limitation + Optional + Priority fee + Represents the minimum gasUsed multiplier required for a transaction to be included in a block. This is the part of the transaction fee that is burnt. + Please align your QR code with the square to scan it. Ensure you scan %s network address. + Recent + Recipient + Not a valid address + Ensure that you are sending funds to an %s wallet address. Errors may result in the loss of your tokens + Send to + A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds + My wallets + A way of measuring Bitcoin transaction fees. It indicates the number of the smallest Bitcoin unit (Satoshi) for each virtual byte in a transaction. The higher the number, the faster the transaction will be processed by miners. + Satoshi / vByte + Sending... + Tap any field to change it + Send %s + You are sending %1$s including a network fee of %2$s + Sending %s + Total + %1$s and %2$s will be sent + ≈ %1$s (inc. fee: %2$s) + %s will be sent + Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while + Invalid address + %1$s (%2$s) + Transaction sent + Store your crypto assets secure while keeping private keys contained in your card + Revolutionary Hardware Wallet + Up to 3 physical cards to one wallet + Ultra Secure Backup + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + Thousands of Currencies + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + The Wallet for Everyone + Meet Tangem + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + Web 3.0 Compatible + The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address. + The amount includes the service provider\'s fee. + Fees + All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval. + Approve + You swap + Give Permission + Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome. + Insufficient funds + Sending amount will be reduced to cover the selected fee level + Approve + Current transaction + The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. + Give Permission + Specify the approve limit for the selected token + Amount %s + To continue, grant 1inch smart contracts permission to use your %s + Unlimited + In progress + Swap + You receive + Choose token + not available + Balances hidden + Balances shown + Undo + This operation is currently unavailable. Please try again later. + Buying %s is not available at the moment. Please check our updates. + You do not have funds to sell. Top up your account to be able to sell funds from it. + You do not have funds to send. Top up your account to be able to send funds from it. + Swapping %s is not available at the moment. Please check our updates. + Selling funds will be available once the pending transaction(s) in network %s is complete + Sending funds will be available once the pending transaction(s) in network %s is complete + Selling %s is not available at the moment. Please check our updates. + Choose address + Generate XPUB + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Hide %s + Hide token + %1$s token in %%image%% %2$s network + Token in %%image%% %1$s network + The %1$s token is the main currency on the %2$s network and cannot be hidden as long as you have other tokens on this network in the list. + Unable to hide %s + Exchange this token for another at %1$s service fees from February %2$s-%3$s. + Swap with Changelly, %s fees + Swap now + contract: %s + You don\'t have any transactions yet + Failed to load transaction history.\nClick on reload button to update the information. + Multiple addresses + Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer. + Operation + from: %s + to: %s + You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d + You\'ve scanned wrong twin card. Please try another one + This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. + One wallet. Two cards. + Scan the card #%s + Creating wallet + Scan the #%s twin card + Preparing card + Tangem Twin + This action is irreversible. You will not have access to the old wallet. + Tap the twin card with number %s and do not remove until the end of the operation + Use %s or scan a card to have an access to your wallet + Add new wallet + Are you sure you want to delete this wallet? + An error has occurred, please scan your card to log in + This wallet has already been saved, you can add another one + Wallet name + Rename Wallet + Unlock all + Unlock all with %s + Blockchain is unreachable. Try later + Scan the card + Requesting to sign a message.\n\n%s + Dapp %1$s, requesting to\nsign BNB transaction.\n\n%2$s + Trade order for %1$s\nPrice: %2$s\nAmount to receive: %3$s\nAmount to pay: %4$s + Transaction details:\nFrom: %1$s\nTo: %2$s\nAmount: %3$s + Clipboard contain WalletConnect code. Use copied value or scan QR-code + Request to create transaction for %1$s\n%2$s\n\nAmount: %3$s\nFee: %4$s\nTotal: %5$s\nBalance: %6$s + Can\'t send transaction. Not enough funds. + Failed to establish WalletConnect session. Please, try again later. + Not all tokens were added to your list. Please add them first and try again. Missing tokens:\n + Failed to sign message.\nPlease, try again + Failed to establish WalletConnect session: timeout error. Please, try again later. + Session request contains unsupported blockchains for WalletConnect connection. Unsupported blockchains:\n + Connection with this Dapp cannot be established due to its technical implementation. + We\'ve encountered unknown error. Error message: %s. If the problem persists — feel free to contact our support + Wrong card selected in Tangem App + Failed to create transaction from Dapp data. Code: %s + We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support + No opened WalletConnect sessions + Ooops. No Sessions. + Failed to pairing WalletConnect session: %1$s + Paste from clipboard + Message for %1$s:\n%2$s + Request to start a session for\n%1$s\n\nNETWORK: %2$s\n\nURL: %3$s + The operation couldn\'t be completed.\n\nYou have already established a WalletConnect session with this parameters. + Scan new code + This card can\'t be used to establish WalletConnect session + This network is not supported. Please select another network. + Select network + WalletConnect Sessions + Connect to dApps + WalletConnect + %s Market Price + last 24h + %s network + Address was copied to clipboard + No internet connection + Tangem + Use %s or scan a card to unlock access to your wallet + Please withdraw all funds from this wallet, reset it to factory settings, and create a new one. Access to the current wallet will be lost. + Activation error + Could be better + Like it + Ok, Got it! + Really cool! + Refresh + You are currently in the Demo mode + Demo mode active + The card you scanned is a developer card. Do not use it to create your wallet. + Not for users! + %1$s network requires an Existential Deposit. If your account drops below %2$s, it will be deactivated, and any remaining funds will be destroyed. + Network requires Existential Deposit + Swap will be available after the %s transaction is complete + You have active transaction + Swap approval is underway and will be completed shortly + Approval in progress + The minimum swapping amount is %1$s. Please ensure that the remaining balance after the swap will not be less than %2$s. + You do not have any %s exchangeable coins in your list + No available tokens to swap + To make a transaction you need to deposit some %1$s %2$s + Unable to cover %s fee + The amount to receive must be at least %s + Service temporarily unavailable + The amount of tokens to be swapped must not exceed %s + The amount to swap must be at least %s + Please change the amount to swap + This card might be a production sample or counterfeit + Authenticity check failed + Associate + This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s + This token must be associated with your Hedera account before you can receive it + Associate your token + Hot enough %s. Top up your Hedera account to associate this token + Only %s signatures are left on this card. You must withdraw all of your funds. + Low signature count + Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. + + Use your card to get an address for %d network + Use your card to get an addresses for %d networks + + Some addresses are missing + The network is currently unreachable. Please try again later. + Network is unreachable + Top up your wallet + Your wallet hasn\'t been backed up. Carry out this procedure to protect your assets now. + Missing backup + This card has been previously used for transactions. If received from an untrusted source, consider withdrawing all funds. If it\'s your card, no action is required. + Card has already signed transactions + Your review keeps us motivated to make Tangem Wallet even better + Enjoying Tangem? + You must associate your token before receiving tokens + Network rent fee required + %1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. + Insufficient %1$s to cover network fee + Transaction pending + The Solana network is congested. If your transaction is not processed within 2 minutes, please repeat the transaction. + Solana Network Alert + Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free. + Some networks currently are unreachable. Please try again later. + Some networks are unreachable + This is a Testnet card. It cannot process transactions and should only be used for testing and development purposes. + For testing purposes only + Discard + You have an interrupted backup. Do you want to resume? + Yes, resume + Discard + If you will discard the backup now, then you will have to reset the cards to factory settings to start over again + Resume backup + This is an irreversible action + Log in with %s + Scan card + Use %s or scan a card to access the app + Welcome back! diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt index a22b3ee620..a111cac1e9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt @@ -161,4 +161,28 @@ internal sealed class SendNotification(val config: NotificationConfig) { ), ) } + + sealed interface Cardano { + + data class MinAdaValueCharged(val tokenName: String, val minAdaValue: String) : Warning( + title = resourceReference(id = R.string.cardano_coin_will_be_send_with_token_title), + subtitle = resourceReference( + id = R.string.cardano_coin_will_be_send_with_token_description, + formatArgs = wrappedList(minAdaValue, tokenName), + ), + ) + + data object InsufficientBalanceToTransferCoin : Error( + title = resourceReference(id = R.string.cardano_max_amount_has_token_title), + subtitle = resourceReference(id = R.string.cardano_max_amount_has_token_description), + ) + + data class InsufficientBalanceToTransferToken(val tokenName: String) : Error( + title = resourceReference(id = R.string.cardano_insufficient_balance_to_send_token_title), + subtitle = resourceReference( + id = R.string.cardano_insufficient_balance_to_send_token_description, + formatArgs = wrappedList(tokenName), + ), + ) + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index baa881f4d6..c0427ecfd2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -1,6 +1,7 @@ package com.tangem.features.send.impl.presentation.state.confirm import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchainsdk.utils.fromNetworkId @@ -8,6 +9,7 @@ import com.tangem.blockchainsdk.utils.minimalAmount import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.extensions.networkIconResId import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase @@ -15,6 +17,8 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.tokens.repository.CurrencyChecksRepository +import com.tangem.domain.tokens.utils.convertToAmount +import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents @@ -23,6 +27,7 @@ import com.tangem.features.send.impl.presentation.state.fee.* import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.utils.getFiatString import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.lib.crypto.BlockchainUtils.isTezos import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList @@ -46,6 +51,7 @@ internal class SendNotificationFactory( private val clickIntents: SendClickIntents, private val analyticsEventHandler: AnalyticsEventHandler, private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, + private val validateTransactionUseCase: ValidateTransactionUseCase, ) { fun create(): Flow> = stateRouterProvider().currentState @@ -77,8 +83,9 @@ internal class SendNotificationFactory( addFeeUnreachableNotification(feeState.feeSelectorState) addExceedBalanceNotification(feeValue, sendingAmount) addExceedsBalanceNotification(feeState.fee) - addDustWarningNotification(feeValue, sendingAmount) + addDustWarningNotificationForSpecificBlockchains(feeValue, sendingAmount) addTransactionLimitErrorNotification(feeValue, sendingAmount) + // warnings addExistentialWarningNotification(feeValue, amountValue) addFeeCoverageNotification( @@ -89,6 +96,9 @@ internal class SendNotificationFactory( addHighFeeWarningNotification(amountValue, sendState.ignoreAmountReduce) addTooHighNotification(feeState.feeSelectorState) addTooLowNotification(feeState) + + // blockchain specific + addCardanoNotifications(sendingAmount, feeState.fee, state) }.toImmutableList() } @@ -275,23 +285,35 @@ internal class SendNotificationFactory( } } - private suspend fun MutableList.addDustWarningNotification( - feeAmount: BigDecimal, - receivedAmount: BigDecimal, + private suspend fun MutableList.addDustWarningNotificationForSpecificBlockchains( + feeValue: BigDecimal, + sendingAmount: BigDecimal, ) { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val dustValue = currencyChecksRepository.getDustValue( - userWalletProvider().walletId, - cryptoCurrencyStatus.currency.network, - ) ?: return + val isCardano = BlockchainUtils.isCardano(cryptoCurrencyStatusProvider().currency.network.id.value) - if (checkDustLimits(feeAmount, receivedAmount, dustValue)) { - add( - SendNotification.Error.MinimumAmountError(dustValue.toPlainString()), - ) + if (!isCardano) { + addDustWarningNotification(feeValue, sendingAmount) } } + private fun checkDustLimits(feeAmount: BigDecimal, receivedAmount: BigDecimal, dustValue: BigDecimal): Boolean { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + + val change = when (cryptoCurrencyStatus.currency) { + is CryptoCurrency.Coin -> { + val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + balance - (feeAmount + receivedAmount) + } + is CryptoCurrency.Token -> { + val balance = coinCryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO + balance - feeAmount + } + } + + val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO + return receivedAmount < dustValue || isChangeLowerThanDust + } + private fun MutableList.addTooLowNotification(feeState: SendStates.FeeState) { val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return @@ -393,13 +415,88 @@ internal class SendNotificationFactory( return Blockchain.fromNetworkId(this.currency.network.backendId) == Blockchain.Arbitrum } - private fun checkDustLimits(feeAmount: BigDecimal, receivedAmount: BigDecimal, dustValue: BigDecimal): Boolean { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + private suspend fun MutableList.addCardanoNotifications( + sendingAmount: BigDecimal, + fee: Fee?, + state: SendUiState, + ) { + val sendingCurrency = cryptoCurrencyStatusProvider().currency + if (!BlockchainUtils.isCardano(sendingCurrency.network.id.value)) return - val totalAmount = feeAmount + receivedAmount - val change = balance - totalAmount - val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO - return receivedAmount < dustValue || isChangeLowerThanDust + validateTransactionUseCase( + amount = sendingAmount.convertToAmount(sendingCurrency), + fee = fee ?: return, + memo = state.recipientState?.memoTextField?.value, + destination = requireNotNull(state.recipientState?.addressTextField?.value), + userWalletId = userWalletProvider().walletId, + network = sendingCurrency.network, + ).fold( + ifLeft = { + addCardanoTransactionValidationError( + error = it as? BlockchainSdkError.Cardano ?: return@fold, + sendingCurrency = sendingCurrency, + sendingAmount = sendingAmount, + fee = fee, + ) + }, + ifRight = { + (fee as? Fee.CardanoToken)?.let { + add( + SendNotification.Cardano.MinAdaValueCharged( + tokenName = sendingCurrency.name, + minAdaValue = it.minAdaValue.parseBigDecimal(sendingCurrency.decimals), + ), + ) + } + }, + ) + } + + private suspend fun MutableList.addCardanoTransactionValidationError( + error: BlockchainSdkError.Cardano, + sendingCurrency: CryptoCurrency, + sendingAmount: BigDecimal, + fee: Fee, + ) { + when (error) { + BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> { + add(SendNotification.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name)) + } + BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> { + when (sendingCurrency) { + is CryptoCurrency.Coin -> SendNotification.Cardano.InsufficientBalanceToTransferCoin + is CryptoCurrency.Token -> { + SendNotification.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name) + } + }.let(::add) + } + BlockchainSdkError.Cardano.InsufficientRemainingBalance, + BlockchainSdkError.Cardano.InsufficientSendingAdaAmount, + -> { + addDustWarningNotification( + feeValue = fee.amount.value ?: BigDecimal.ZERO, + sendingAmount = sendingAmount, + ) + } + } + } + + private suspend fun MutableList.addDustWarningNotification( + feeValue: BigDecimal, + sendingAmount: BigDecimal, + ) { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val dustValue = currencyChecksRepository.getDustValue( + userWalletProvider().walletId, + cryptoCurrencyStatus.currency.network, + ) ?: return + + if (checkDustLimits(feeValue, sendingAmount, dustValue)) { + add( + SendNotification.Error.MinimumAmountError( + amount = dustValue.parseBigDecimal(cryptoCurrencyStatus.currency.decimals), + ), + ) + } } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 5410b54f17..c90a473a60 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -30,10 +30,7 @@ import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.transaction.error.GetFeeError -import com.tangem.domain.transaction.usecase.CreateTransactionUseCase -import com.tangem.domain.transaction.usecase.GetFeeUseCase -import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase -import com.tangem.domain.transaction.usecase.SendTransactionUseCase +import com.tangem.domain.transaction.usecase.* import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase import com.tangem.domain.wallets.models.UserWallet @@ -95,6 +92,7 @@ internal class SendViewModel @Inject constructor( private val neverShowTapHelpUseCase: NeverShowTapHelpUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val listenToQrScanningUseCase: ListenToQrScanningUseCase, + validateTransactionUseCase: ValidateTransactionUseCase, currencyChecksRepository: CurrencyChecksRepository, isFeeApproximateUseCase: IsFeeApproximateUseCase, validateWalletMemoUseCase: ValidateWalletMemoUseCase, @@ -173,6 +171,7 @@ internal class SendViewModel @Inject constructor( clickIntents = this, analyticsEventHandler = analyticsEventHandler, getBalanceNotEnoughForFeeWarningUseCase = getBalanceNotEnoughForFeeWarningUseCase, + validateTransactionUseCase = validateTransactionUseCase, ) private val sendScreenAnalyticSender by lazy(LazyThreadSafetyMode.NONE) { diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index a4492852b0..4571c8a31e 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -44,4 +44,9 @@ object BlockchainUtils { val blockchain = Blockchain.fromId(networkId) return blockchain == Blockchain.Tezos } + + fun isCardano(networkId: String): Boolean { + val blockchain = Blockchain.fromId(networkId) + return blockchain == Blockchain.Cardano + } } \ No newline at end of file From 011bd368899612a98aeba86110c9a41dc6aded57 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 May 2024 17:40:27 +0300 Subject: [PATCH 64/98] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 7478a68a2b..403b0b5d7b 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ room = "2.6.1" # region Tangem tangemBlockchainSdk = "develop-624" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-351" +tangemCardSdk = "develop-354" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem From 7e79305db773a36018c8d9109f2559e201a7070e Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 May 2024 19:22:01 +0300 Subject: [PATCH 65/98] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 07cfce37ff..a494a9b493 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 07cfce37ff84e70081aca82d9948acb13c5c07b0 +Subproject commit a494a9b49335d891dc5836d478c843432a857621 diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index e8ac19e194..00ee28bafb 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -338,7 +338,6 @@ private val excludedBlockchains = listOf( Blockchain.Unknown, Blockchain.Nexa, Blockchain.NexaTestnet, - Blockchain.Radiant, Blockchain.Manta, Blockchain.MantaTestnet, Blockchain.Mantle, From 55942f2208ec3e233531dec522c4ec1ddd3d1e70 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 May 2024 18:07:42 +0800 Subject: [PATCH 66/98] Updated on 2026-08-14 --- .../tap/proxy/TransactionManagerImpl.kt | 39 ++-- .../DefaultTransactionRepository.kt | 7 +- .../transaction/TransactionRepository.kt | 6 +- .../state/confirm/SendNotificationFactory.kt | 16 +- features/swap/domain/build.gradle.kts | 1 + .../swap/domain/models/domain/Warning.kt | 9 + .../feature/swap/domain/SwapInteractorImpl.kt | 187 ++++++++++++++---- .../swap/domain/di/SwapDomainModule.kt | 2 + features/swap/presentation/build.gradle.kts | 5 +- .../feature/swap/models/SwapStateHolder.kt | 11 ++ .../tangem/feature/swap/ui/StateBuilder.kt | 45 +++++ .../feature/swap/ui/SwapScreenContent.kt | 3 +- .../com/tangem/lib/crypto/models/ProxyFee.kt | 21 +- 13 files changed, 281 insertions(+), 71 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index 934f519967..799274946e 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -224,26 +224,33 @@ class TransactionManagerImpl( when (fee.data) { is TransactionFee.Single -> { val normalFee = (fee.data as TransactionFee.Single).normal - val singleFee = ProxyFee( - gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = normalFee.amount), - ) - ProxyFees.SingleFee( - singleFee = singleFee, - ) + val singleFee = if (normalFee as? Fee.CardanoToken != null) { + ProxyFee.CardanoToken( + gasLimit = BigInteger.ZERO, + fee = convertToProxyAmount(amount = normalFee.amount), + minAdaValue = normalFee.minAdaValue, + ) + } else { + ProxyFee.Common( + gasLimit = BigInteger.ZERO, + fee = convertToProxyAmount(amount = normalFee.amount), + ) + } + + ProxyFees.SingleFee(singleFee = singleFee) } is TransactionFee.Choosable -> { val choosableFee = fee.data as TransactionFee.Choosable ProxyFees.MultipleFees( - minFee = ProxyFee( + minFee = ProxyFee.Common( gasLimit = BigInteger.ZERO, fee = convertToProxyAmount(amount = choosableFee.minimum.amount), ), - normalFee = ProxyFee( + normalFee = ProxyFee.Common( gasLimit = BigInteger.ZERO, fee = convertToProxyAmount(amount = choosableFee.normal.amount), ), - priorityFee = ProxyFee( + priorityFee = ProxyFee.Common( gasLimit = BigInteger.ZERO, fee = convertToProxyAmount(amount = choosableFee.priority.amount), ), @@ -300,15 +307,15 @@ class TransactionManagerImpl( is Result.Success -> { val choosableFee = fee.data - val minProxyFee = ProxyFee( + val minProxyFee = ProxyFee.Common( gasLimit = (choosableFee.minimum as Fee.Ethereum).gasLimit, fee = convertToProxyAmount(amount = choosableFee.minimum.amount), ) - val normalProxyFee = ProxyFee( + val normalProxyFee = ProxyFee.Common( gasLimit = (choosableFee.normal as Fee.Ethereum).gasLimit, fee = convertToProxyAmount(amount = choosableFee.normal.amount), ) - val priorityProxyFee = ProxyFee( + val priorityProxyFee = ProxyFee.Common( gasLimit = (choosableFee.priority as Fee.Ethereum).gasLimit, fee = convertToProxyAmount(amount = choosableFee.priority.amount), ) @@ -463,7 +470,7 @@ class TransactionManagerImpl( scale = blockchain.decimals(), mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN), ) - val minFee = ProxyFee( + val minFee = ProxyFee.Common( gasLimit = gasLimit, fee = ProxyAmount( currencySymbol = blockchain.currency, @@ -471,7 +478,7 @@ class TransactionManagerImpl( decimals = blockchain.decimals(), ), ) - val normalFee = ProxyFee( + val normalFee = ProxyFee.Common( gasLimit = gasLimit, fee = ProxyAmount( currencySymbol = blockchain.currency, @@ -479,7 +486,7 @@ class TransactionManagerImpl( decimals = blockchain.decimals(), ), ) - val priorityFee = ProxyFee( + val priorityFee = ProxyFee.Common( gasLimit = gasLimit, fee = ProxyAmount( currencySymbol = blockchain.currency, diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index cad609ee54..4016636877 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -57,7 +57,7 @@ internal class DefaultTransactionRepository( override suspend fun validateTransaction( amount: Amount, - fee: Fee, + fee: Fee?, memo: String?, destination: String, userWalletId: UserWalletId, @@ -65,9 +65,10 @@ internal class DefaultTransactionRepository( isSwap: Boolean, hash: String?, ): Result { + val blockchain = Blockchain.fromId(network.id.value) val walletManager = walletManagersStore.getSyncOrNull( userWalletId = userWalletId, - blockchain = Blockchain.fromId(network.id.value), + blockchain = blockchain, derivationPath = network.derivationPath.value, ) @@ -76,7 +77,7 @@ internal class DefaultTransactionRepository( return if (validator != null) { val transaction = walletManager.createTransactionInternal( amount = amount, - fee = fee, + fee = fee ?: Fee.Common(amount = amount), memo = memo, destination = destination, network = network, diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt index 4acf080799..156e2bb0fa 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt @@ -25,13 +25,13 @@ interface TransactionRepository { @Suppress("LongParameterList") suspend fun validateTransaction( amount: Amount, - fee: Fee, + fee: Fee?, memo: String?, destination: String, userWalletId: UserWalletId, network: Network, - isSwap: Boolean, - hash: String?, + isSwap: Boolean = false, + hash: String? = null, ): Result suspend fun sendTransaction( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index c0427ecfd2..57bb2ccffc 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -435,8 +435,6 @@ internal class SendNotificationFactory( addCardanoTransactionValidationError( error = it as? BlockchainSdkError.Cardano ?: return@fold, sendingCurrency = sendingCurrency, - sendingAmount = sendingAmount, - fee = fee, ) }, ifRight = { @@ -455,8 +453,6 @@ internal class SendNotificationFactory( private suspend fun MutableList.addCardanoTransactionValidationError( error: BlockchainSdkError.Cardano, sendingCurrency: CryptoCurrency, - sendingAmount: BigDecimal, - fee: Fee, ) { when (error) { BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> { @@ -473,9 +469,15 @@ internal class SendNotificationFactory( BlockchainSdkError.Cardano.InsufficientRemainingBalance, BlockchainSdkError.Cardano.InsufficientSendingAdaAmount, -> { - addDustWarningNotification( - feeValue = fee.amount.value ?: BigDecimal.ZERO, - sendingAmount = sendingAmount, + val dustValue = currencyChecksRepository.getDustValue( + userWalletId = userWalletProvider().walletId, + network = sendingCurrency.network, + ) ?: return + + add( + SendNotification.Error.MinimumAmountError( + amount = dustValue.parseBigDecimal(sendingCurrency.decimals), + ), ) } } diff --git a/features/swap/domain/build.gradle.kts b/features/swap/domain/build.gradle.kts index 10ccde5b9f..73348d6d0b 100644 --- a/features/swap/domain/build.gradle.kts +++ b/features/swap/domain/build.gradle.kts @@ -47,5 +47,6 @@ dependencies { implementation(deps.arrow.core) implementation(deps.timber) implementation(deps.tangem.blockchain) + implementation(deps.tangem.card.core) implementation(deps.moshi) } \ No newline at end of file diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/Warning.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/Warning.kt index 318b5635de..a7e59700b2 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/Warning.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/Warning.kt @@ -9,4 +9,13 @@ sealed class Warning { data class MinAmountWarning(val dustValue: BigDecimal) : Warning() data class ReduceAmountWarning(val tezosFeeThreshold: BigDecimal) : Warning() + + sealed class Cardano : Warning() { + + data class MinAdaValueCharged(val tokenName: String, val minAdaValue: String) : Cardano() + + data object InsufficientBalanceToTransferCoin : Cardano() + + data class InsufficientBalanceToTransferToken(val tokenName: String) : Cardano() + } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 8deb74e13e..8bba53e589 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -5,9 +5,11 @@ import arrow.core.getOrElse import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchainsdk.utils.minimalAmount +import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.appcurrency.repository.AppCurrencyRepository @@ -21,6 +23,8 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.utils.convertToAmount +import com.tangem.domain.transaction.TransactionRepository +import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.domain.transaction.usecase.CreateTransactionUseCase import com.tangem.domain.transaction.usecase.EstimateFeeUseCase @@ -36,15 +40,12 @@ import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.swap.domain.models.toStringWithRightOffset import com.tangem.feature.swap.domain.models.ui.* +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager -import com.tangem.lib.crypto.models.AnalyticsData -import com.tangem.lib.crypto.models.ApproveTxData -import com.tangem.lib.crypto.models.ProxyAmount -import com.tangem.lib.crypto.models.ProxyFees +import com.tangem.lib.crypto.models.* import com.tangem.lib.crypto.models.transactions.SendTxResult import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.isNullOrZero import com.tangem.utils.toFiatString import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.firstOrNull @@ -73,6 +74,7 @@ internal class SwapInteractorImpl @Inject constructor( private val currenciesRepository: CurrenciesRepository, private val initialToCurrencyResolver: InitialToCurrencyResolver, private val demoConfig: DemoConfig, + private val transactionRepository: TransactionRepository, ) : SwapInteractor { private val estimateFeeUseCase by lazy(LazyThreadSafetyMode.NONE) { @@ -350,6 +352,7 @@ internal class SwapInteractorImpl @Inject constructor( isAllowedToSpend = isAllowedToSpend, isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough, txFee = TxFeeState.Empty, + transactionFee = null, includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex ) } @@ -379,6 +382,7 @@ internal class SwapInteractorImpl @Inject constructor( fromTokenStatus: CryptoCurrencyStatus, amount: SwapAmount, feeState: TxFeeState, + minAdaValue: BigDecimal?, ): List { val fromToken = fromTokenStatus.currency val userWalletId = getSelectedWallet()?.walletId ?: return emptyList() @@ -386,6 +390,13 @@ internal class SwapInteractorImpl @Inject constructor( manageExistentialDepositWarning(warnings, userWalletId, amount, fromToken) manageDustWarning(warnings, feeState, userWalletId, fromTokenStatus, amount) manageReduceAmountWarning(warnings, fromTokenStatus, amount) + manageCardanoTransactionValidationWarnings( + warnings = warnings, + fromToken = fromToken, + amount = amount, + userWalletId = userWalletId, + minAdaValue = minAdaValue, + ) return warnings } @@ -414,23 +425,35 @@ internal class SwapInteractorImpl @Inject constructor( fromTokenStatus: CryptoCurrencyStatus, amount: SwapAmount, ) { + if (BlockchainUtils.isCardano(fromTokenStatus.currency.network.id.value)) return + val fee = when (feeState) { TxFeeState.Empty -> BigDecimal.ZERO is TxFeeState.MultipleFeeState -> feeState.priorityFee.feeValue is TxFeeState.SingleFeeState -> feeState.fee.feeValue } - val dust = currencyChecksRepository.getDustValue(userWalletId, fromTokenStatus.currency.network) - val balance = fromTokenStatus.value.amount ?: BigDecimal.ZERO - if (dust != null && - !balance.isNullOrZero() && - amount.value < balance - ) { - val change = balance - (amount.value + fee) - val isChangeLowerThanDust = change < dust && change != BigDecimal.ZERO - val isShowWarning = amount.value + fee < dust || isChangeLowerThanDust - if (isShowWarning) { - warnings.add(Warning.MinAmountWarning(dust)) + + val dustValue = currencyChecksRepository.getDustValue(userWalletId, fromTokenStatus.currency.network) ?: return + + val change = when (fromTokenStatus.currency) { + is CryptoCurrency.Coin -> { + val balance = fromTokenStatus.value.amount ?: BigDecimal.ZERO + balance - (fee + amount.value) } + is CryptoCurrency.Token -> { + val nativeTokenBalance = userWalletManager.getNativeTokenBalance( + fromTokenStatus.currency.network.id.value, + fromTokenStatus.currency.network.derivationPath.value, + ) + + nativeTokenBalance?.value?.minus(fee) ?: BigDecimal.ZERO + } + } + + val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO + + if (amount.value < dustValue || isChangeLowerThanDust) { + warnings.add(Warning.MinAmountWarning(dustValue)) } } @@ -445,6 +468,72 @@ internal class SwapInteractorImpl @Inject constructor( } } + private suspend fun manageCardanoTransactionValidationWarnings( + warnings: MutableList, + fromToken: CryptoCurrency, + amount: SwapAmount, + userWalletId: UserWalletId, + minAdaValue: BigDecimal?, + ) { + transactionRepository.validateTransaction( + amount = amount.value.convertToAmount(fromToken), + fee = null, + memo = null, + destination = getTokenAddress(fromToken), + userWalletId = userWalletId, + network = fromToken.network, + ) + .fold( + onFailure = { + addCardanoTransactionValidationError( + warnings = warnings, + error = it as? BlockchainSdkError.Cardano ?: return@fold, + fromToken = fromToken, + userWalletId = userWalletId, + ) + }, + onSuccess = { + minAdaValue?.let { + warnings.add( + Warning.Cardano.MinAdaValueCharged( + tokenName = fromToken.name, + minAdaValue = minAdaValue.parseBigDecimal(fromToken.decimals), + ), + ) + } + }, + ) + } + + private suspend fun addCardanoTransactionValidationError( + warnings: MutableList, + error: BlockchainSdkError.Cardano, + fromToken: CryptoCurrency, + userWalletId: UserWalletId, + ) { + when (error) { + BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> { + Warning.Cardano.InsufficientBalanceToTransferToken(fromToken.name) + } + BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> { + when (fromToken) { + is CryptoCurrency.Coin -> Warning.Cardano.InsufficientBalanceToTransferCoin + is CryptoCurrency.Token -> { + Warning.Cardano.InsufficientBalanceToTransferToken(fromToken.name) + } + } + } + BlockchainSdkError.Cardano.InsufficientRemainingBalance, + BlockchainSdkError.Cardano.InsufficientSendingAdaAmount, + -> { + val dustValue = currencyChecksRepository.getDustValue(userWalletId, fromToken.network) ?: return + + Warning.MinAmountWarning(dustValue) + } + } + .let(warnings::add) // add warning to the list + } + override suspend fun onSwap( swapProvider: SwapProvider, swapData: SwapDataModel?, @@ -841,8 +930,16 @@ internal class SwapInteractorImpl @Inject constructor( val fromToken = fromTokenStatus.currency val toToken = toTokenStatus.currency return coroutineScope { + val txFeeResult = getSelectedWalletSyncUseCase().getOrNull()?.walletId?.let { userWalletId -> + getUnhandledFee( + amount = amount.value, + userWalletId = userWalletId, + cryptoCurrency = fromToken, + ) + } + val txFee = if (provider.type == ExchangeProviderType.CEX) { - getFeeForCex(amount, fromTokenStatus) + getFeeForCex(txFeeResult, fromTokenStatus) } else { TxFeeState.Empty } @@ -881,11 +978,13 @@ internal class SwapInteractorImpl @Inject constructor( isAllowedToSpend = isAllowedToSpend, isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough, txFee = txFee, + transactionFee = txFeeResult?.getOrNull(), includeFeeInAmount = includeFeeInAmount, ) } } + @Suppress("LongMethod") private suspend fun getQuotesState( exchangeProviderType: ExchangeProviderType, quoteDataModel: Either, @@ -896,6 +995,7 @@ internal class SwapInteractorImpl @Inject constructor( isAllowedToSpend: Boolean, isBalanceWithoutFeeEnough: Boolean, txFee: TxFeeState, + transactionFee: TransactionFee?, includeFeeInAmount: IncludeFeeInAmount, ): SwapState { return quoteDataModel.fold( @@ -909,7 +1009,12 @@ internal class SwapInteractorImpl @Inject constructor( swapData = null, txFeeState = txFee, ).copy( - warnings = manageWarnings(fromToken, amount, txFee), + warnings = manageWarnings( + fromTokenStatus = fromToken, + amount = amount, + feeState = txFee, + minAdaValue = (transactionFee?.normal as? Fee.CardanoToken)?.minAdaValue, + ), ) when (exchangeProviderType) { @@ -1113,7 +1218,14 @@ internal class SwapInteractorImpl @Inject constructor( ) swapState.copy( permissionState = PermissionDataState.Empty, - warnings = manageWarnings(fromToken, amount, txFeeState), + warnings = manageWarnings( + fromToken, + amount, + txFeeState, + (feeData as? ProxyFees.SingleFee)?.let { + (it.singleFee as? ProxyFee.CardanoToken)?.minAdaValue + }, + ), preparedSwapConfigState = PreparedSwapConfigState( isAllowedToSpend = true, isBalanceEnough = isBalanceIncludeFeeEnough, @@ -1179,23 +1291,26 @@ internal class SwapInteractorImpl @Inject constructor( ) } - private suspend fun getFeeForCex(amount: SwapAmount, fromToken: CryptoCurrencyStatus): TxFeeState { - getSelectedWalletSyncUseCase().getOrNull()?.walletId?.let { userWalletId -> - val txFeeResult = estimateFeeUseCase( - amount = amount.value, - userWalletId = userWalletId, - cryptoCurrency = fromToken.currency, - ).firstOrNull() - return txFeeResult?.fold( - ifLeft = { - TxFeeState.Empty - }, - ifRight = { txFee -> - txFee.toTxFeeState(fromToken.currency) - }, - ) ?: TxFeeState.Empty - } - return TxFeeState.Empty + private suspend fun getFeeForCex( + txFeeResult: Either?, + fromToken: CryptoCurrencyStatus, + ): TxFeeState { + return txFeeResult?.fold( + ifLeft = { TxFeeState.Empty }, + ifRight = { txFee -> txFee.toTxFeeState(fromToken.currency) }, + ) ?: TxFeeState.Empty + } + + private suspend fun getUnhandledFee( + amount: BigDecimal, + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + ): Either? { + return estimateFeeUseCase( + amount = amount, + userWalletId = userWalletId, + cryptoCurrency = cryptoCurrency, + ).firstOrNull() } @Suppress("LongParameterList", "LongMethod") diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index 9c2290da01..1a6a6e8b22 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -50,6 +50,7 @@ class SwapDomainModule { coroutineDispatcherProvider: CoroutineDispatcherProvider, initialToCurrencyResolver: InitialToCurrencyResolver, currenciesRepository: CurrenciesRepository, + transactionRepository: TransactionRepository, ): SwapInteractor { return SwapInteractorImpl( transactionManager = transactionManager, @@ -69,6 +70,7 @@ class SwapDomainModule { currenciesRepository = currenciesRepository, initialToCurrencyResolver = initialToCurrencyResolver, demoConfig = DemoConfig(), + transactionRepository = transactionRepository, ) } diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index c78375b5e4..643cf8f201 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { implementation(projects.domain.balanceHiding.models) implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) + implementation(projects.domain.transaction) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) implementation(projects.domain.settings) @@ -56,6 +57,9 @@ dependencies { implementation(projects.features.swap.api) implementation(projects.features.tokendetails.api) + /** Libs */ + implementation(projects.libs.crypto) + /** Other libraries */ implementation(deps.compose.shimmer) implementation(deps.compose.accompanist.webView) @@ -67,5 +71,4 @@ dependencies { /** DI */ implementation(deps.hilt.android) kapt(deps.hilt.kapt) - } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index 74d4f48e01..cd22de2a3b 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -110,6 +110,7 @@ sealed interface SwapWarning { val type: GenericWarningType = GenericWarningType.OTHER, val onClick: () -> Unit, ) : SwapWarning + data class GeneralError(val notificationConfig: NotificationConfig) : SwapWarning data class UnableToCoverFeeWarning(val notificationConfig: NotificationConfig) : SwapWarning data class GeneralWarning(val notificationConfig: NotificationConfig) : SwapWarning @@ -117,6 +118,16 @@ sealed interface SwapWarning { data class TransactionInProgressWarning(val title: TextReference, val description: TextReference) : SwapWarning data class NeedReserveToCreateAccount(val notificationConfig: NotificationConfig) : SwapWarning data class ReduceAmount(val notificationConfig: NotificationConfig) : SwapWarning + + sealed interface Cardano : SwapWarning { + val notificationConfig: NotificationConfig + + data class MinAdaValueCharged(override val notificationConfig: NotificationConfig) : Cardano + + data class InsufficientBalanceToTransferCoin(override val notificationConfig: NotificationConfig) : Cardano + + data class InsufficientBalanceToTransferToken(override val notificationConfig: NotificationConfig) : Cardano + } } enum class GenericWarningType { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index b4557850a7..4e12e4c227 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -405,6 +405,15 @@ internal class StateBuilder( ), ) } + Warning.Cardano.InsufficientBalanceToTransferCoin -> { + warnings.add(createInsufficientBalanceToTransferCoin()) + } + is Warning.Cardano.InsufficientBalanceToTransferToken -> { + warnings.add(createInsufficientBalanceToTransferToken(tokenName = it.tokenName)) + } + is Warning.Cardano.MinAdaValueCharged -> { + warnings.add(createMinAdaValueCharged(minAdaValue = it.minAdaValue, tokenName = it.tokenName)) + } } } } @@ -1400,6 +1409,42 @@ internal class StateBuilder( iconResId = R.drawable.img_attention_20, ) } + + private fun createMinAdaValueCharged(minAdaValue: String, tokenName: String): SwapWarning { + return SwapWarning.Cardano.MinAdaValueCharged( + NotificationConfig( + title = resourceReference(id = R.string.cardano_coin_will_be_send_with_token_title), + subtitle = resourceReference( + id = R.string.cardano_coin_will_be_send_with_token_description, + formatArgs = wrappedList(minAdaValue, tokenName), + ), + iconResId = R.drawable.img_attention_20, + ), + ) + } + + private fun createInsufficientBalanceToTransferCoin(): SwapWarning { + return SwapWarning.Cardano.InsufficientBalanceToTransferCoin( + NotificationConfig( + title = resourceReference(id = R.string.cardano_max_amount_has_token_title), + subtitle = resourceReference(id = R.string.cardano_max_amount_has_token_description), + iconResId = R.drawable.img_attention_20, + ), + ) + } + + private fun createInsufficientBalanceToTransferToken(tokenName: String): SwapWarning { + return SwapWarning.Cardano.InsufficientBalanceToTransferToken( + NotificationConfig( + title = resourceReference(id = R.string.cardano_insufficient_balance_to_send_token_title), + subtitle = resourceReference( + id = R.string.cardano_insufficient_balance_to_send_token_description, + formatArgs = wrappedList(tokenName), + ), + iconResId = R.drawable.img_attention_20, + ), + ) + } // end region private fun getShortAddressValue(fullAddress: String): String { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 1137f0f264..0cdecf9771 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -391,7 +391,8 @@ private fun SwapWarnings(warnings: List) { }, ) } - else -> {} + is SwapWarning.Cardano -> Notification(config = warning.notificationConfig) + SwapWarning.InsufficientFunds -> Unit } SpacerH8() } diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFee.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFee.kt index 56b92d5688..a4643460ea 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFee.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFee.kt @@ -1,8 +1,21 @@ package com.tangem.lib.crypto.models +import java.math.BigDecimal import java.math.BigInteger -data class ProxyFee( - val gasLimit: BigInteger, - val fee: ProxyAmount, -) \ No newline at end of file +sealed interface ProxyFee { + + val gasLimit: BigInteger + val fee: ProxyAmount + + data class Common( + override val gasLimit: BigInteger, + override val fee: ProxyAmount, + ) : ProxyFee + + data class CardanoToken( + override val gasLimit: BigInteger, + override val fee: ProxyAmount, + val minAdaValue: BigDecimal, + ) : ProxyFee +} \ No newline at end of file From 6cf85272f35a727c2ece881ade57001bfc480865 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 May 2024 02:22:35 +0800 Subject: [PATCH 67/98] Updated on 2026-08-14 --- .../features/send/redux/middlewares/SendMiddleware.kt | 10 +++------- .../com/tangem/tap/proxy/TransactionManagerImpl.kt | 4 ++-- .../main/assets/configs/feature_toggles_config.json | 6 +++++- .../data/transaction/DefaultTransactionRepository.kt | 4 ++-- gradle/dependencies.toml | 2 +- libs/blockchain-sdk/build.gradle.kts | 1 + .../blockchainsdk/DefaultBlockchainSDKFactory.kt | 5 ++++- .../blockchainsdk/WalletManagerFactoryCreator.kt | 6 ++++++ .../blockchainsdk/di/BlockchainSDKFactoryModule.kt | 4 ++++ .../featuretoggles/BlockchainSDKFeatureToggles.kt | 6 ++++++ .../DefaultBlockchainSDKFeatureToggles.kt | 11 +++++++++++ 11 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/featuretoggles/BlockchainSDKFeatureToggles.kt create mode 100644 libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/featuretoggles/DefaultBlockchainSDKFeatureToggles.kt diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 519637bd2f..86a40c5ccf 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -4,7 +4,7 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchain.blockchains.algorand.AlgorandTransactionExtras import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras -import com.tangem.blockchain.blockchains.hedera.HederaTransactionBuilder +import com.tangem.blockchain.blockchains.hedera.HederaTransactionExtras import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.ton.TonTransactionExtras @@ -20,6 +20,7 @@ import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext +import com.tangem.domain.demo.DemoTransactionSender import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.tap.common.analytics.events.Token @@ -31,7 +32,6 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TangemSigner import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage -import com.tangem.domain.demo.DemoTransactionSender import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.FeeAction.RequestFee @@ -170,11 +170,7 @@ private fun sendTransaction( transactionExtras.xrpDestinationTag?.tag?.let { txData = txData.copy(extras = XrpTransactionExtras(it)) } transactionExtras.cosmosMemoState?.memo?.let { txData = txData.copy(extras = CosmosTransactionExtras(it)) } transactionExtras.tonMemoState?.memo?.let { txData = txData.copy(extras = TonTransactionExtras(it)) } - transactionExtras.hederaMemoState?.memo?.let { - txData = txData.copy( - extras = HederaTransactionBuilder.HederaTransactionExtras(it), - ) - } + transactionExtras.hederaMemoState?.memo?.let { txData = txData.copy(extras = HederaTransactionExtras(it)) } transactionExtras.algorandMemoState?.memo?.let { txData = txData.copy(extras = AlgorandTransactionExtras(it)) } scope.launch { diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index 799274946e..a04a551fc2 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -8,7 +8,7 @@ import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager -import com.tangem.blockchain.blockchains.hedera.HederaTransactionBuilder +import com.tangem.blockchain.blockchains.hedera.HederaTransactionExtras import com.tangem.blockchain.blockchains.optimism.EthereumOptimisticRollupWalletManager import com.tangem.blockchain.blockchains.stellar.StellarMemo import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras @@ -141,7 +141,7 @@ class TransactionManagerImpl( Blockchain.XRP -> memo.toLongOrNull()?.let { XrpTransactionBuilder.XrpTransactionExtras(it) } Blockchain.Cosmos -> CosmosTransactionExtras(memo) Blockchain.TON -> TonTransactionExtras(memo) - Blockchain.Hedera -> HederaTransactionBuilder.HederaTransactionExtras(memo) + Blockchain.Hedera -> HederaTransactionExtras(memo) Blockchain.Algorand -> AlgorandTransactionExtras(memo) else -> null } diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 7de6a6f134..d3be4f5db5 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -13,7 +13,7 @@ }, { "name": "LOCAL_USER_LOGS_ENABLED", - "version": "5.10.0" + "version": "5.11.0" }, { "name": "GENERATE_XPUB_ENABLED", @@ -26,5 +26,9 @@ { "name": "TOKEN_LIST_LCE_ENABLED", "version": "5.10.0" + }, + { + "name": "CARDANO_TOKENS_SUPPORT_ENABLED", + "version": "5.11.0" } ] diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index 4016636877..96b20e9c0c 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -4,7 +4,7 @@ import androidx.core.text.isDigitsOnly import com.tangem.blockchain.blockchains.algorand.AlgorandTransactionExtras import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras -import com.tangem.blockchain.blockchains.hedera.HederaTransactionBuilder +import com.tangem.blockchain.blockchains.hedera.HederaTransactionExtras import com.tangem.blockchain.blockchains.stellar.StellarMemo import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.ton.TonTransactionExtras @@ -147,7 +147,7 @@ internal class DefaultTransactionRepository( Blockchain.TerraV2, -> CosmosTransactionExtras(memo) Blockchain.TON -> TonTransactionExtras(memo) - Blockchain.Hedera -> HederaTransactionBuilder.HederaTransactionExtras(memo) + Blockchain.Hedera -> HederaTransactionExtras(memo) Blockchain.Algorand -> AlgorandTransactionExtras(memo) else -> null } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 403b0b5d7b..67d4201243 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -86,7 +86,7 @@ room = "2.6.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-624" +tangemBlockchainSdk = "develop-628" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-354" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/blockchain-sdk/build.gradle.kts b/libs/blockchain-sdk/build.gradle.kts index 88d96a0405..f9c7b8a359 100644 --- a/libs/blockchain-sdk/build.gradle.kts +++ b/libs/blockchain-sdk/build.gradle.kts @@ -13,6 +13,7 @@ dependencies { // region Core modules implementation(projects.core.datasource) + implementation(projects.core.featuretoggles) implementation(projects.core.utils) // endregion diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt index 406be2d877..7adfd7b9e1 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt @@ -13,8 +13,10 @@ import com.tangem.datasource.config.models.ConfigValueModel import com.tangem.datasource.config.models.ProviderModel import com.tangem.libs.blockchain_sdk.BuildConfig import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import timber.log.Timber internal typealias BlockchainProvidersResponse = Map> @@ -57,6 +59,7 @@ internal class DefaultBlockchainSDKFactory( return combine( flow = configStore.get(), flow2 = blockchainProviderTypesStore.get(), + // flow3 = subscribe on feature toggles changes, TODO: [REDACTED_JIRA] transform = walletManagerFactoryCreator::create, ) .stateIn(scope = mainScope, started = SharingStarted.Eagerly, initialValue = null) diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt index feae18a4e4..9ede47a471 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt @@ -1,10 +1,12 @@ package com.tangem.blockchainsdk import com.tangem.blockchain.common.AccountCreator +import com.tangem.blockchain.common.BlockchainFeatureToggles import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.common.datastorage.BlockchainDataStorage import com.tangem.blockchain.common.logging.BlockchainSDKLogger +import com.tangem.blockchainsdk.featuretoggles.BlockchainSDKFeatureToggles import timber.log.Timber import javax.inject.Inject @@ -21,6 +23,7 @@ internal class WalletManagerFactoryCreator @Inject constructor( private val accountCreator: AccountCreator, private val blockchainDataStorage: BlockchainDataStorage, private val blockchainSDKLogger: BlockchainSDKLogger, + private val blockchainSDKFeatureToggles: BlockchainSDKFeatureToggles, ) { fun create(config: BlockchainSdkConfig, blockchainProviderTypes: BlockchainProviderTypes): WalletManagerFactory { @@ -30,6 +33,9 @@ internal class WalletManagerFactoryCreator @Inject constructor( config = config, blockchainProviderTypes = blockchainProviderTypes, accountCreator = accountCreator, + featureToggles = BlockchainFeatureToggles( + isCardanoTokenSupport = blockchainSDKFeatureToggles.isCardanoTokensSupportEnabled, + ), blockchainDataStorage = blockchainDataStorage, loggers = listOf(blockchainSDKLogger), ) diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt index 3ebbe75b92..7adb1648a3 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt @@ -7,8 +7,10 @@ import com.tangem.blockchainsdk.DefaultBlockchainSDKFactory import com.tangem.blockchainsdk.WalletManagerFactoryCreator import com.tangem.blockchainsdk.accountcreator.DefaultAccountCreator import com.tangem.blockchainsdk.datastorage.DefaultBlockchainDataStorage +import com.tangem.blockchainsdk.featuretoggles.DefaultBlockchainSDKFeatureToggles import com.tangem.blockchainsdk.loader.BlockchainProvidersResponseLoader import com.tangem.blockchainsdk.store.DefaultRuntimeStore +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.asset.loader.AssetLoader @@ -49,11 +51,13 @@ internal object BlockchainSDKFactoryModule { tangemTechApi: TangemTechApi, appPreferencesStore: AppPreferencesStore, blockchainSDKLogger: BlockchainSDKLogger, + featureTogglesManager: FeatureTogglesManager, ): WalletManagerFactoryCreator { return WalletManagerFactoryCreator( accountCreator = DefaultAccountCreator(authProvider, tangemTechApi), blockchainDataStorage = DefaultBlockchainDataStorage(appPreferencesStore), blockchainSDKLogger = blockchainSDKLogger, + blockchainSDKFeatureToggles = DefaultBlockchainSDKFeatureToggles(featureTogglesManager), ) } } \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/featuretoggles/BlockchainSDKFeatureToggles.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/featuretoggles/BlockchainSDKFeatureToggles.kt new file mode 100644 index 0000000000..fcdc2099b3 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/featuretoggles/BlockchainSDKFeatureToggles.kt @@ -0,0 +1,6 @@ +package com.tangem.blockchainsdk.featuretoggles + +internal interface BlockchainSDKFeatureToggles { + + val isCardanoTokensSupportEnabled: Boolean +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/featuretoggles/DefaultBlockchainSDKFeatureToggles.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/featuretoggles/DefaultBlockchainSDKFeatureToggles.kt new file mode 100644 index 0000000000..ce7241d7d5 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/featuretoggles/DefaultBlockchainSDKFeatureToggles.kt @@ -0,0 +1,11 @@ +package com.tangem.blockchainsdk.featuretoggles + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager + +internal class DefaultBlockchainSDKFeatureToggles( + private val featureTogglesManager: FeatureTogglesManager, +) : BlockchainSDKFeatureToggles { + + override val isCardanoTokensSupportEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled(name = "CARDANO_TOKENS_SUPPORT_ENABLED") +} \ No newline at end of file From 51fdf6467719fb932ca339d03feaf92a5bf8e09a Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 May 2024 15:57:29 +0300 Subject: [PATCH 68/98] Updated on 2026-08-14 --- .../tangem/tap/di/domain/CardDomainModule.kt | 2 +- .../tap/di/domain/SettingsDomainModule.kt | 2 - .../DefaultDeleteSavedAccessCodesUseCase.kt | 2 +- .../repository/DelegatedKeystoreManager.kt | 11 +- core/res/src/main/res/values-ru/strings.xml | 1431 ++++++++--------- core/res/src/main/res/values/strings.xml | 1427 ++++++++-------- .../data/settings/di/SettingsDataModule.kt | 4 - .../tangem/feature/swap/ui/StateBuilder.kt | 1 - .../components/TokenDetailsNotification.kt | 8 - .../TokenDetailsNotificationConverter.kt | 1 - gradle/dependencies.toml | 2 +- 11 files changed, 1428 insertions(+), 1463 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index d34f20eb74..f49789df71 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -8,8 +8,8 @@ import com.tangem.domain.demo.DemoConfig import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase -import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase +import com.tangem.tap.domain.sdk.TangemSdkManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index ecb92a6ac9..22502ca5c5 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -9,8 +9,6 @@ import com.tangem.domain.settings.* import com.tangem.domain.settings.repositories.AppRatingRepository import com.tangem.domain.settings.repositories.PromoSettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository -import com.tangem.tap.domain.TangemSdkManager -import com.tangem.domain.settings.repositories.SwapPromoRepository import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository import dagger.Module diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt index b0fadb5bcf..8834604935 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt @@ -6,7 +6,7 @@ import arrow.core.right import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.domain.card.DeleteSavedAccessCodesUseCase -import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.sdk.TangemSdkManager internal class DefaultDeleteSavedAccessCodesUseCase( private val tangemSdkManager: TangemSdkManager, diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt index 3db56b3dcb..7e7a781189 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt @@ -8,20 +8,15 @@ internal class DelegatedKeystoreManager( private val keystoreManagerProvider: Provider, ) : KeystoreManager { - override suspend fun get( - masterKeyConfig: KeystoreManager.MasterKeyConfig, - keyAlias: String, - forceAuthentication: Boolean, - ): SecretKey? { - return keystoreManagerProvider().get(masterKeyConfig, keyAlias, forceAuthentication) + override suspend fun get(masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAlias: String): SecretKey? { + return keystoreManagerProvider().get(masterKeyConfig, keyAlias) } override suspend fun get( masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAliases: Set, - forceAuthentication: Boolean, ): Map { - return keystoreManagerProvider().get(masterKeyConfig, keyAliases, forceAuthentication) + return keystoreManagerProvider().get(masterKeyConfig, keyAliases) } override suspend fun store(masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAlias: String, key: SecretKey) { diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 7d27d1a437..53f6a46e4e 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1,722 +1,715 @@ - Добавить токен - Валюты - Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств. - Обратиться в поддержку - Попробовать снова - Эта функция недоступна в демонстрационном режиме - Причина: %s - Не могу отправить транзакцию - Выбранный кошелёк не поддерживает сеть %1$s - Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. - Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. - Спасибо за ваш отзыв. Мы ответим в кратчайшие сроки. - Ваши предложения отправлены - Пожалуйста, попробуйте приложить карту в точности, как показано на анимации, или запросите поддержку. - У вас возникли трудности со сканированием карты? - Эта карта не предназначена для работы с этим приложением - Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. - Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem - Включите биометрическую аутентификацию - Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. - При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены из приложения. - Сохранение кода доступа - Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. - Cохранение кошелька - Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. - Тёмная - Светлая - Как в системе - При выборе настройки как в системе приложение будет использовать тему в соответствии с настройками вашего устройства - Системная - Тема - Настройки приложения - Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" - Больше не показывать - Понятно - Балансы скрыты - Пожалуйста, отсканируйте карту - Пожалуйста, попробуйте снова через 30 секунд или отсканируйте карту - Слишком много попыток - Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона. - Начать резервное копирование - - %d карта - %d карты - %d карт - %d карт - - Отключите эту опцию, если не хотите, чтобы эта карта использовалась для сброса кодов доступа на другие карты этого кошелька. Обратите внимание, сброс кода также не будет доступен на этой карте. - Использовать эту карту для сброса кода доступа на других картах в этом кошельке - Восстановление кода доступа - Сбросить - Вы уверены, что хотите это сделать? - Смена кода доступа - Код доступа будет изменен только на данной карте - Заводские настройки - Тип безопасности - Настройки карты - Помимо сетевой комиссий, сеть Cardano взимает %1$s ADA при транзакции с токеном %2$s - Требования к транзакции Cardano - Чтобы совершить транзакцию %1$s, внесите некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA (рекомендуется 5 ADA) - Недостаточно ADA для транзакции - Вы должны поддерживать некоторое количество ADA, поскольку у вас на балансе есть токены в сети Cardano - Недостаточно ADA - Принять - Доступ запрещен - Применить - Одобрение - Внимание - Баланс: %s - Баланс - биометрическую аутентификацию - биометрией - Купить - Перейти на %1$s - Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. - Отмена - Закрыть - Продолжить - Копировать - Скопировать адрес - Создать - Удалить - Отключено - Отключить - Готово - Включить - Включено - Ошибка - Обозреватель - Посмотреть историю транзакций - Обозреватель - Комиссия - Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s - Свое - Быстро - По рынку - Медленно - Скорость и комиссия - Получить адреса - К провайдеру - Импортировать - Заблокирован - Основная сеть - Сетевая комиссия - Далее - Нет - Нет адреса - OK - Основная карта - Парольная фраза - Вставить - Подробнее - Получить - Отклонить - Перезагрузить - Переименовать - Повторить - Сохранить изменения - Искать - Поиск токенов - Seed-фраза - Выберите действие - Продать - Отправить - Сервер недоступен, повторите попытку позднее - Поделиться - Подписать - Подписать и отправить - Начать - Отправить - Успешно - Поддержка - Обмен - условия участия - Ошибка транзакции - Транзакции - Перевод - Я понял - Произошла ошибка. Пожалуйста, попробуйте снова. - Недоступно - Предупреждение - Да - Адрес контракта скопирован! - Доступные сети - Добавить токен - Адрес контракта - Адрес контракта некорректен - Пожалуйста, выберите сеть - Десятичное число должно быть действительным целым числом, до %li - Своя деривация - Например m/00\'/0000\'/0\'/0/0 - Введите свою деривацию - Знаков после запятой - Путь деривации - По умолчанию - Деривация по BIP44 - Введенный путь деривации некорректен - Например, USD Coin - Название токена - Не выбрано - Сеть - Сеть - Вы можете добавить токен в ручную, если он не поддерживается Tangem - Например, USDC - Символ - Символ токена - Этот токен/сеть уже находится в вашем списке - Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить - Остерегайтесь мошеннических токенов, они могут ничего не стоить - Токены могут быть созданы кем угодно - Чат - Код доступа - Перед сканированием карты вам нужно будет ввести правильный код доступа. - Задержка сканирования - Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. - Пароль - Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. - Реферальная программа - Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы - %s хэшей - Номер карты - Обратиться в поддержку - Добавить еще карты - Валюта приложения - Скрывать балансы жестом переворота - Эмитент - Подписано - Если вы забудете код, то потеряете доступ к своим средствам. Восстановление кода невозможно. - Подробности - Проверьте подключение с интернетом или переключитесь на другую сеть - Условия использования - Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. - Мои токены - У вас нет добавленных токенов. Добавьте токены для обмена - Недоступен для обмена с %s - Предоставлено - Статус - Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами - Выберите провайдера - Произошла ошибка. Код: %s - К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) - Выбранный провайдер недоступен для обмена. Попробуйте позже. (Код: %s) - В данный момент обмен невозможен. Попробуйте позже. (Код: %s) - Курс обмена - Обмен через %s - Чтобы вернуть ваши деньги, посетите сайт провайдера - Операция не выполнена провайдером - Посетите сайт провайдера для проверки - Провайдер запрашивает прохождение верификации - Отменен - Подтверждено - Подтверждение - Подтверждение... - Обменяно - Обмен - Обмен... - Неудачно - Депозит получен - Ожидание депозита - Ожидаем пополнения... - Возвращено - Отправляем - Отправка средств... - Отправлено - Данные провайдера. Сумма к получению может измениться в зависимости от рыночных условий. - Статус обмена - Требуется верификация - Список токенов в вашем кошельке - Получение наилучших курсов... - Плавающая ставка - Пользуясь сервисом, вы соглашаетесь с %s - Пользуясь сервисом, вы соглашаетесь с %1$s и %2$s - Больше провайдеров на подходе.\nСледите за обновлениями! - Политикой конфиденциальности - Провайдер - Лучший курс - Доступно до %s - Доступно с %s - Недоступно для этой пары - Требуется разрешение - Условиями использования - Токены не найдены. Пожалуйста, попробуйте другой запрос - ID транзакции скопирован - Информация ниже не является обязательной. Вы можете стереть её, если хотите. - Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. - Скажите, пожалуйста, какая у вас карта? - Привет, команда поддержки, - Пожалуйста, расскажите нам больше о вашей проблеме. Каждая маленькая деталь может помочь. - Мои предложения - Не могу отсканировать карту - Обращение в поддержку - Обращение в поддержку Tangem - Не могу отправить транзакцию - Купить - Сканировать - Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции - Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции - Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции - Приложите, чтобы отсканировать - Приложите, чтобы подписать - Приложите карту - Вы обновили данные биометрии, отсканируйте свою карту для входа - Чтобы начать отслеживать свои криптоактивы и транзакции, добавьте токены - Управление токенами - Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту - Отсканируйте карту - Обменивайте свои токены с %1$s комиссии провайдера через Changelly с %2$s по %3$s февраля. - Обмен с Changelly, %s комиссии - Токены - Добавить - Изменить - Рыночная капитализация - Основная сеть - Не основной или основной блокчейн, на котором размещен токен - Не основные сети - Выберите сети - Кошелек - Не удалось найти этот токен, вы можете добавить его вручную. - - %1$d из %2$d кошелька - %1$d из %2$d кошельков - %1$d из %2$d кошельков - %1$d из %2$d кошельков - - например Bitcoin - Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление. - Голосовать - Выберите кошелек - Кошелёк не поддерживает более одной сети - Вам необходимо установить единый код доступа для защиты всех ваших карт - Защита - Позже вы сможете установить индивидуальный код доступа для каждой карты - Персонализация - Код доступа можно восстановить с помощью привязанной карты. Не храните все карты в одном месте. - Восстановление - Выберите любое слово, фразу или число в качестве кода доступа - Создайте код доступа - Введите код доступа еще раз, чтобы избежать ошибки - Повторно введите код доступа - Код доступа должен состоять не менее чем из 4 символов. - Введенные коды доступа не совпадают - Необходимо повторить операцию, при этом карта будет сброшена к заводским настройкам - Ошибка активации - Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? - Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. - Парольная фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. - Добавить резервную карту - Сканировать карту #%d - Создать резервную копию - Сканировать основную карту - Перейти к моему кошельку - Завершение бэкапа - Получить криптовалюту - Сканировать основную карту - Пропустить - Как это работает? - Давайте сгенерируем все ключи на вашей карте и создадим безопасный кошелек - Создать кошелек - Создать кошелек - Другие опции - Ваши ключи будут надежно сгенерированы внутри карты. Никакой seed-фразы, а это значит, что никто не может экспортировать или украсть ее. - Cоздавайте ключи приватно - Ваша карта активирована и готова к использованию - Успешно! - В этом случае вам будет необходимо начать процесс заново. - Вы хотите выйти из процесса активации? - Подготовка - Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Если на нем есть средства, пожалуйста сначала выведите их, а затем сделайте сброс до заводских настроек и используйте как резервную. - Резервная копия - Прочитать о seed-фразе - - - Запишите эти %d слова в порядке, указанном ниже, и сохраните их в надежном месте. - Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. - Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. - - Ваша seed-фраза - - - %d слова - %d слов - %d слов - - Чтобы импортировать кошелек, введите seed-фразу в поле ниже - Создать seed-фразу - Импорт кошелька - Seed-фраза — это набор слов, который дает возможность восстановить кошелек. В отличие от ключей, сгенерированных картой, seed-фраза не защищена и может быть скопирована и украдена. Используйте этот вариант на свой страх и риск. - Использовать seed-фразу - Неверная seed-фраза. Пожалуйста, проверьте порядок слов. - Неверная seed-фраза. Пожалуйста, проверьте орфографию. - Устаревший - Чтобы проверить, правильно ли вы записали seed-фразу, введите 2-е, 7-е и 11-е слова - Итак, проверим - Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. - Вы можете добавить еще одну карту или завершить процесс резервного копирования - Подготовьте резервную карту с номером %s - Отсканируйте основную карту, чтобы начать процесс резервного копирования. - Подготовьте основную карту с номером %s - Ваша карта настроена и готова к использованию. - Добавлено максимальное количество карт. Завершите процесс резервного копирования. - Активация карты - Резервная карта #%d - Нет резервных карт - Добавлена ​​одна резервная карта - Подготовьте свою карту - Добавлены две резервные карты - Пополните кошелек на любую сумму, чтобы начать пользоваться картой - Пополните кошелек более чем на %1$s %2$s, чтобы начать пользоваться картой - Купить криптовалюту - Показать адрес кошелька - Активация кошелька - Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. - Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала - Вы можете сделать резервную копию своих ключей на одной или двух других пустых картах Wallet. - Код доступа можно восстановить с помощью одной из резервных карт. - Все резервные карты являются полнофункциональными и содержат одинаковые ключи. - Вы сможете установить код доступа для защиты своих кошельков. - Резервная копия карты - Восстановление кода доступа - Идентичные карты - Код доступа - Группы - По балансу - Сортировка токенов - Список - Выбрать из галереи - Нравится наше приложение? - %1$s (%2$s) в сети %3$s - Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств. - Участвовать - Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. - Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. - Грядущие выплаты - Ваши друзья купили - Меньше - Больше - Нет грядущих выплат - - за %d кошелек - за %d кошелька - за %d кошельков - за %d кошельков - - Получите ^^%1$s^^ на ваш адрес в сети %2$s %3$s ^^спустя 30 дней^^ за каждый кошелек, который купит ваш друг - Вы - Получит - при покупке кошелька на сайте tangem.com - %s скидку - Ваш друг - Персональный код скопирован! - Ваш персональный код - Купи Tangem Wallet со скидкой!\n%s - Приведи друга в Tangem - Вы приняли - Нажимая на эту кнопку, вы принимаете - в реферальной программе - - %d кошелек - %d кошелька - %d кошельков - %d кошельков - - Сбросить карту - Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку - Я понимаю, что не смогу этой картой восстановить пароль на остальных картах этого кошелька, если я его забуду - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать данную карту для восстановления кода доступа. - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек. - У вас есть карта банка другой страны, а также вид на жительство или регистрация вне РФ? - Карты банков РФ в данный момент не принимаются - Войдите в приложение и следите за своим балансом без сканирования карты - Доступ в приложение - Использовать биометрию - Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты - Код доступа - Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков - Включите биометрическую аутентификацию - Вы хотите использовать биометрию? - Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта - Сканировать - Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. - Приготовьте свою карту - Уже содержится в введенном адресе - Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. - Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? - Причина: %1$s\nКод: %2$s - Транзакция не выполнена - Сумма - %1$s, %2$s - Адрес - Код назначения - Введите адрес - Адрес совпадает с адресом кошелька - Недопустимый Tag. Он не будет добавлен в транзакцию. - Недопустимый Memo. Он не будет добавлен в транзакцию. - Tag - Memo - Включая комиссию - Низкая - Нормальная - Приоритетная - Проверьте своё интернет соединение - Информация о комиссии сети недоступна - Из - Лимит газа - Это максимальное количество газа, которое будет потрачено на выполнение транзакции или контракта. Лимит газа предотвращает неожиданные или неограниченные расходы при выполнении транзакции. - Цена газа - Это стоимость, которую вы готовы заплатить за каждую единицу газа. Чем выше цена газа, тем быстрее ваша транзакция будет обработана. - Всё - Максимальная сумма - Комиссия не превысит - Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение. - Допустим ввод только цифр - Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии - Покрытие сетевой комиссии - Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса - Недостаточно средств - Оставить %s - Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе. - Экзистенциальный депозит - Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. - Установлена высокая комиссия - Ввиду особенности сети Tezos комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %s. - Комиссия повышена - Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению - Недопустимая сумма - Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %2$s. - Адрес получателя не активирован. \nПожалуйста, измените сумму отправки, чтобы продолжить. - Сумма отправки не может быть менее %s - Уменьшить на %s - Уменьшить до %s - Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции - Возможны задержки по транзакции - Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. - Лимит транзакции - Опционально - Пожалуйста, совместите свой QR-код с квадратом, чтобы отсканировать его. Убедитесь, что вы сканируете адрес в сети %s. - Последние - Получатель - Неверный адрес - Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов - Отправить - Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. - Мои кошельки - Способ измерения комиссии за биткоин-транзакцию. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый виртуальный байт в транзакции. Чем выше число, тем быстрее будет обработана транзакция майнерами. - Сатоши / вбайт - Отправка - Нажмите на любое поле, чтобы изменить его - Отправка %s - Вы отправляете %1$s, включая комиссию сети %2$s - Отправка %s - Всего - %1$s и %2$s будет отправлено - ≈ %1$s (вкл. комиссию: %2$s) - %s будет отправлено - Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время - Неверный адрес - Транзакция отправлена - Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. - Революционный аппаратный кошелек - До трех карт с одним кошельком - Все ключи в безопасности - Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте - Тысячи криптовалют - Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. - Кошелек для каждого - Встречайте Tangem - Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах - Поддержка Web 3.0 - В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя - В сумму включена комиссия провайдера сервиса. - Комиссии - Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен. - Подтвердить - Вы отправляете - Дать разрешение - Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму. - Недостаточно средств - Сумма отправки будет уменьшена для покрытия выбранного уровня комиссии - Подтвердить - Текущая транзакция - Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена. - Дать разрешение - Укажите лимит доступа к выбранному токену - Количество %s - Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s - Безлимитно - В процессе - Обменять - Вы получите - Выберите токен - не доступен - Балансы скрыты - Балансы показаны - Отменить - Выбранная операция в данный момент недоступна. Попробуйте позже. - В данный момент покупка монеты %s недоступна. Следите за нашими обновлениями. - У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. - У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. - В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. - Продажа средств станет доступной после завершения транзакции(-ий) в сети %s - Отправка средств станет доступной после завершения транзакции(-ий) в сети %s - В данный момент продажа %s недоступна. Следите за нашими обновлениями. - Выберите адрес - Сгенерировать XPUB - Скрыть - Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. - Скрыть %s - Скрыть токен - %1$s токен в сети %%image%% %2$s - Токен в сети %%image%% %1$s - Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. - Невозможно скрыть %s - Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля. - Обмен с Changelly, %s комиссии - Обменять - контракт: %s - У вас еще нет транзакций - Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию. - Несколько адресов - История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе. - Операция - от: %s - на: %s - Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d - Вы отсканировали не ту twin-карту. Пожалуйста, попробуйте отсканировать другую - Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька. - Один кошелек. Две карты. - Сканировать карту #%s - Создание кошелька - Отсканируйте twin-карту #%s - Подготовка карты - Tangem Twin - Это действие необратимо. У вас не будет доступа к старому кошельку. - Приложите twin-карту с номером %s и не убирайте до окончания операции - Используйте %s или отсканируйте карту, чтобы получить доступ к своему кошельку - Добавить новый кошелек - Вы уверены, что хотите удалить этот кошелек? - Произошла ошибка, пожалуйста, отсканируйте свою карту для входа - Этот кошелек уже был сохранен, вы можете добавить другой - Имя кошелька - Переименование кошелька - Разблокировать все - Разблокировать все с %s - Блокчейн недоступен. Попробуйте позже. - Отсканируйте карту - Запрос на подпись сообщения.\n\n%s - Dapp %1$s, запрос на\nподпись транзакции с BNB.\n\n%2$s - Торговый ордер на %1$s\nЦена: %2$s\nСумма к получению: %3$s\nСумма к оплате: %4$s - Детали транзакции:\nОт: %1$s\nК: %2$s\nСумма: %3$s - Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код - Запрос на создание транзакции для %1$s\n%2$s\n\nСумма: %3$s\nКомиссия: %4$s\nВсего: %5$s\nБаланс: %6$s - Невозможно отправить транзакцию. Недостаточно средств. - Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. - Не все токены добавлены в ваш список. Пожалуйста, добавьте их в начале, а потом попробуйте снова. Недостающие токены: \n - Не удалось подписать сообщение.\nПожалуйста, попробуйте еще раз - Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже. - Запрос на подключение через WalletConnect содержит неподдерживаемые блокчеины. Неподдерживаемые блокчеины:\n - Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. - Произошла непредвиденная ошибка. Сообщение ошибки: %s Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. - Неверная карта выбрана в приложении Tangem - Не удалось создать транзакцию из данных Dapp. Код: %s - Произошла непредвиденная ошибка. Код ошибки: %d Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. - Нет открытых сессий WalletConnect - Упс. Нет сессий. - Не удалось создать пару WalletConnect: %1$s - Вставить из буфера обмена - Сообщение для %1$s:\n%2$s - Запрос на открытие сессии для\n%1$s\n\nСЕТЬ: %2$s\n\nURL: %3$s - Операция не может быть завершена.\n\nВы уже установили сеанс WalletConnect с этими параметрами. - Сканировать новый код - Эту карту нельзя использовать с WalletConnect. - Сеть не поддерживается. Пожалуйста, выберите другую сеть. - Выберите сеть - Сессии WalletConnect - Подключение к dApps - WalletConnect - Рыночная цена %s - за 24 часа - Сеть %s - Адрес скопирован в буфер обмена - Нет соединения с интернетом - Tangem - Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку - Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян. - Ошибка активации - Можно лучше - Нравится - Понятно! - Очень круто! - Обновить - Вы находитесь в режиме демо - Демо режим включен - Отсканированная вами карта является картой разработчика. Не используйте ее для создания своего кошелька. - Не для пользователя! - Cеть %1$s использует концепцию экзистенциального депозита. Если баланс вашего счета будет ниже %2$s, то он будет деактивирован, а средства на счете уничтожены. - Для работы с сетью необходим депозит - Обмен будет доступен после завершения %s транзакции - У вас есть активная транзакция - Разрешение обмена в процессе и будет скоро завершено - Разрешение в процессе - Минимальная сумма обмена - %1$s. Пожалуйста, убедитесь, что остаток после обмена также не будет меньше %2$s. - У вас в списке нет монет доступных для обмена с %s - Нет доступных для обмена токенов - Чтобы совершить транзакцию, вам необходимо внести немного %1$s %2$s - Невозможно покрыть комиссию %s - Сумма получения не может быть менее %s - Cервис временно недоступен - Сумма для обмена должна быть не более %s - Сумма для обмена должна быть не менее %s - Пожалуйста, измените сумму для обмена - Возможно, данная карта - образец или подделка - Ошибка проверки подлинности - Ассоциировать - Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%1$s %2$s - Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять - Ассоциируете свой токен - Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена - На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. - Малое количество подписей - Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. - - Используйте вашу карту, чтобы получить адрес для %d сети - Используйте вашу карту, чтобы получить адреса для %d сетей - Используйте вашу карту, чтобы получить адреса для %d сетей - Используйте вашу карту, чтобы получить адреса для %d сетей - - Некоторые адреса отсутствуют - В данный момент сеть недоступна. Пожалуйста, попробуйте позже. - Сеть недоступна - Пополните ваш кошелек - Ваш кошелек не имеет резервной копии. Проведите эту процедуру сейчас, чтобы защитить ваши активы. - Резервная копия отсутствует - Эта карта ранее использовалась для подписи транзакций. Если она получена от ненадежного источника, рассмотрите возможность вывода своих средств. Если это ваша карта, дополнительных действий не требуется. - Карта уже подписывала транзакции - Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше - Нравится Tangem? - Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его - Необходима плата за аренду сети - %1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети. - Недостаточно %1$s для оплаты комиссии сети - Транзакция в обработке - Сеть Солана испытывает высокую нагрузку. Если Ваша транзакция не прошла в течение 2 минут, повторите её отправку. - Оповещение сети Солана - Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату. - Некоторые сети в настоящее время недоступны. Пожалуйста, повторите попытку позже. - Некоторые сети недоступны - Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки. - Только для целей тестирования - Отказаться - Вы не закончили резервное копирование. Хотите продолжить? - Да, возобновить - Отказаться - Если сейчас отказаться, то придётся сбрасывать карты до заводских настроек, чтобы начать заново - Возобновить резервное копирование - Это необратимое действие - Войти с %s - Сканировать карту - Используйте %s или отсканируйте карту для входа в приложение - C возвращением! + Добавить токен + Валюты + Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств. + Обратиться в поддержку + Эта функция недоступна в демонстрационном режиме + Причина: %s + Не могу отправить транзакцию + Выбранный кошелёк не поддерживает сеть %1$s + Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. + Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. + У вас возникли трудности со сканированием карты? + Эта карта не предназначена для работы с этим приложением + Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. + Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem + Включите биометрическую аутентификацию + Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. + При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены из приложения. + Сохранение кода доступа + Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. + Cохранение кошелька + Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. + Тёмная + Светлая + Как в системе + Тема + Настройки приложения + Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" + Больше не показывать + Понятно + Балансы скрыты + Пожалуйста, отсканируйте карту + Пожалуйста, попробуйте снова через 30 секунд или отсканируйте карту + Слишком много попыток + Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона. + Начать резервное копирование + + %d карта + %d карты + %d карт + %d карт + + Отключите эту опцию, если не хотите, чтобы эта карта использовалась для сброса кодов доступа на другие карты этого кошелька. Обратите внимание, сброс кода также не будет доступен на этой карте. + Использовать эту карту для сброса кода доступа на других картах в этом кошельке + Восстановление кода доступа + Сбросить + Вы уверены, что хотите это сделать? + Смена кода доступа + Код доступа будет изменен только на данной карте + Заводские настройки + Тип безопасности + Настройки карты + Помимо сетевой комиссий, сеть Cardano взимает %1$s ADA при транзакции с токеном %2$s + Требования к транзакции Cardano + Чтобы совершить транзакцию %1$s, внесите некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA (рекомендуется 5 ADA) + Недостаточно ADA для транзакции + Вы должны поддерживать некоторое количество ADA, поскольку у вас на балансе есть токены в сети Cardano + Недостаточно ADA + Принять + Доступ запрещен + Применить + Одобрение + Внимание + Баланс: %s + Баланс + биометрическую аутентификацию + биометрией + Купить + Перейти на %1$s + Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. + Отмена + Закрыть + Продолжить + Копировать + Скопировать адрес + Создать + Удалить + Отключено + Готово + Включить + Включено + Ошибка + Обозреватель + Посмотреть историю транзакций + Обозреватель + Комиссия + Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s + Свое + Быстро + По рынку + Медленно + Скорость и комиссия + Получить адреса + К провайдеру + Импортировать + Заблокирован + Основная сеть + Сетевая комиссия + Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии + Далее + Нет + Нет адреса + OK + Основная карта + Парольная фраза + Вставить + Подробнее + Получить + Отклонить + Перезагрузить + Переименовать + Сохранить изменения + Искать + Поиск токенов + Seed-фраза + Выберите действие + Продать + Отправить + Сервер недоступен, повторите попытку позднее + Поделиться + Подписать + Подписать и отправить + Начать + Отправить + Успешно + Поддержка + Обмен + условия участия + Ошибка транзакции + Транзакции + Перевод + Я понял + Произошла ошибка. Пожалуйста, попробуйте снова. + Недоступно + Да + Адрес контракта скопирован! + Доступные сети + Добавить токен + Адрес контракта + Адрес контракта некорректен + Пожалуйста, выберите сеть + Десятичное число должно быть действительным целым числом, до %li + Своя деривация + Например m/00\'/0000\'/0\'/0/0 + Введите свою деривацию + Знаков после запятой + Путь деривации + По умолчанию + Деривация по BIP44 + Введенный путь деривации некорректен + Например, USD Coin + Название токена + Не выбрано + Сеть + Сеть + Вы можете добавить токен в ручную, если он не поддерживается Tangem + Например, USDC + Символ + Символ токена + Этот токен/сеть уже находится в вашем списке + Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить + Остерегайтесь мошеннических токенов, они могут ничего не стоить + Токены могут быть созданы кем угодно + Чат + Код доступа + Перед сканированием карты вам нужно будет ввести правильный код доступа. + Задержка сканирования + Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. + Пароль + Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. + Реферальная программа + Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы + %s хэшей + Номер карты + Обратиться в поддержку + Добавить еще карты + Валюта приложения + Скрывать балансы жестом переворота + Эмитент + Подписано + Подробности + Проверьте подключение с интернетом или переключитесь на другую сеть + Условия использования + Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. + Мои токены + У вас нет добавленных токенов. Добавьте токены для обмена + Недоступен для обмена с %s + Предоставлено + Статус + Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами + Выберите провайдера + Произошла ошибка. Код: %s + К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) + Выбранный провайдер недоступен для обмена. Попробуйте позже. (Код: %s) + В данный момент обмен невозможен. Попробуйте позже. (Код: %s) + Курс обмена + Обмен через %s + Чтобы вернуть ваши деньги, посетите сайт провайдера + Операция не выполнена провайдером + Посетите сайт провайдера для проверки + Провайдер запрашивает прохождение верификации + Отменен + Подтверждено + Подтверждение + Подтверждение... + Обменяно + Обмен + Обмен... + Неудачно + Депозит получен + Ожидание депозита + Ожидаем пополнения... + Возвращено + Отправляем + Отправка средств... + Отправлено + Данные провайдера. Сумма к получению может измениться в зависимости от рыночных условий. + Статус обмена + Требуется верификация + Список токенов в вашем кошельке + Получение наилучших курсов... + Плавающая ставка + Пользуясь сервисом, вы соглашаетесь с %s + Пользуясь сервисом, вы соглашаетесь с %1$s и %2$s + Больше провайдеров на подходе.\nСледите за обновлениями! + Политикой конфиденциальности + Провайдер + Лучший курс + Доступно до %s + Доступно с %s + Недоступно для этой пары + Требуется разрешение + Условиями использования + Токены не найдены. Пожалуйста, попробуйте другой запрос + ID транзакции скопирован + Информация ниже не является обязательной. Вы можете стереть её, если хотите. + Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. + Скажите, пожалуйста, какая у вас карта? + Привет, команда поддержки, + Пожалуйста, расскажите нам больше о вашей проблеме. Каждая маленькая деталь может помочь. + Мои предложения + Не могу отсканировать карту + Обращение в поддержку + Обращение в поддержку Tangem + Не могу отправить транзакцию + Купить + Сканировать + Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции + Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции + Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции + Приложите, чтобы отсканировать + Приложите, чтобы подписать + Приложите карту + Вы обновили данные биометрии, отсканируйте свою карту для входа + Чтобы начать отслеживать свои криптоактивы и транзакции, добавьте токены + Управление токенами + Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту + Отсканируйте карту + Обменивайте свои токены с %1$s комиссии провайдера через Changelly с %2$s по %3$s февраля. + Обмен с Changelly, %s комиссии + Токены + Забронировать + Оплатите его криптой и сэкономьте **50 долларов** через нашего партнера Travala: **%1s - %2s** + Забронируйте отпуск с Tangem + Добавить + Изменить + Рыночная капитализация + Основная сеть + Не основной или основной блокчейн, на котором размещен токен + Не основные сети + Выберите сети + Кошелек + Не удалось найти этот токен, вы можете добавить его вручную. + + %1$d из %2$d кошелька + %1$d из %2$d кошельков + %1$d из %2$d кошельков + %1$d из %2$d кошельков + + например Bitcoin + Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление. + Голосовать + Выберите кошелек + Кошелёк не поддерживает более одной сети + Вам необходимо установить единый код доступа для защиты всех ваших карт + Защита + Позже вы сможете установить индивидуальный код доступа для каждой карты + Персонализация + Код доступа можно восстановить с помощью привязанной карты. Не храните все карты в одном месте. + Восстановление + Выберите любое слово, фразу или число в качестве кода доступа + Создайте код доступа + Введите код доступа еще раз, чтобы избежать ошибки + Повторно введите код доступа + Код доступа должен состоять не менее чем из 4 символов. + Введенные коды доступа не совпадают + Необходимо повторить операцию, при этом карта будет сброшена к заводским настройкам + Ошибка активации + Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? + Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. + Парольная фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. + Добавить резервную карту + Сканировать карту #%d + Создать резервную копию + Сканировать основную карту + Перейти к моему кошельку + Завершение бэкапа + Получить криптовалюту + Сканировать основную карту + Пропустить + Как это работает? + Давайте сгенерируем все ключи на вашей карте и создадим безопасный кошелек + Создать кошелек + Создать кошелек + Другие опции + Ваши ключи будут надежно сгенерированы внутри карты. Никакой seed-фразы, а это значит, что никто не может экспортировать или украсть ее. + Cоздавайте ключи приватно + Ваша карта активирована и готова к использованию + Успешно! + В этом случае вам будет необходимо начать процесс заново. + Вы хотите выйти из процесса активации? + Подготовка + Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Если на нем есть средства, пожалуйста сначала выведите их, а затем сделайте сброс до заводских настроек и используйте как резервную. + Резервная копия + Прочитать о seed-фразе + + + Запишите эти %d слова в порядке, указанном ниже, и сохраните их в надежном месте. + Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. + Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. + + Ваша seed-фраза + + + %d слова + %d слов + %d слов + + Чтобы импортировать кошелек, введите seed-фразу в поле ниже + Создать seed-фразу + Импорт кошелька + Seed-фраза — это набор слов, который дает возможность восстановить кошелек. В отличие от ключей, сгенерированных картой, seed-фраза не защищена и может быть скопирована и украдена. Используйте этот вариант на свой страх и риск. + Использовать seed-фразу + Неверная seed-фраза. Пожалуйста, проверьте порядок слов. + Неверная seed-фраза. Пожалуйста, проверьте орфографию. + Устаревший + Чтобы проверить, правильно ли вы записали seed-фразу, введите 2-е, 7-е и 11-е слова + Итак, проверим + Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. + Вы можете добавить еще одну карту или завершить процесс резервного копирования + Подготовьте резервную карту с номером %s + Отсканируйте основную карту, чтобы начать процесс резервного копирования. + Подготовьте основную карту с номером %s + Ваша карта настроена и готова к использованию. + Добавлено максимальное количество карт. Завершите процесс резервного копирования. + Активация карты + Резервная карта #%d + Нет резервных карт + Добавлена ​​одна резервная карта + Подготовьте свою карту + Добавлены две резервные карты + Пополните кошелек на любую сумму, чтобы начать пользоваться картой + Пополните кошелек более чем на %1$s %2$s, чтобы начать пользоваться картой + Купить криптовалюту + Показать адрес кошелька + Активация кошелька + Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. + Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала + Вы можете сделать резервную копию своих ключей на одной или двух других пустых картах Wallet. + Код доступа можно восстановить с помощью одной из резервных карт. + Все резервные карты являются полнофункциональными и содержат одинаковые ключи. + Вы сможете установить код доступа для защиты своих кошельков. + Резервная копия карты + Восстановление кода доступа + Идентичные карты + Код доступа + Группы + По балансу + Сортировка токенов + Список + Выбрать из галереи + Нравится наше приложение? + %1$s (%2$s) в сети %3$s + Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств. + Участвовать + Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. + Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. + Грядущие выплаты + Ваши друзья купили + Меньше + Больше + Нет грядущих выплат + + за %d кошелек + за %d кошелька + за %d кошельков + за %d кошельков + + Получите ^^%1$s^^ на ваш адрес в сети %2$s %3$s ^^спустя 30 дней^^ за каждый кошелек, который купит ваш друг + Вы + Получит + при покупке кошелька на сайте tangem.com + %s скидку + Ваш друг + Персональный код скопирован! + Ваш персональный код + Купи Tangem Wallet со скидкой!\n%s + Приведи друга в Tangem + Вы приняли + Нажимая на эту кнопку, вы принимаете + в реферальной программе + + %d кошелек + %d кошелька + %d кошельков + %d кошельков + + Сбросить карту + Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку + Я понимаю, что не смогу этой картой восстановить пароль на остальных картах этого кошелька, если я его забуду + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать данную карту для восстановления кода доступа. + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек. + У вас есть карта банка другой страны, а также вид на жительство или регистрация вне РФ? + Карты банков РФ в данный момент не принимаются + Войдите в приложение и следите за своим балансом без сканирования карты + Доступ в приложение + Использовать биометрию + Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты + Код доступа + Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков + Включите биометрическую аутентификацию + Вы хотите использовать биометрию? + Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта + Сканировать + Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. + Приготовьте свою карту + Уже содержится в введенном адресе + Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. + Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? + Причина: %1$s\nКод: %2$s + Транзакция не выполнена + Сумма + Вы можете установить комиссию за транзакцию, изменив значение в поле Satoshi per vByte. + %1$s, %2$s + Адрес + Код назначения + Введите адрес + Адрес совпадает с адресом кошелька + Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение. + Недопустимый Tag. Он не будет добавлен в транзакцию. + Недопустимый Memo. Он не будет добавлен в транзакцию. + Tag + Memo + Включая комиссию + Низкая + Нормальная + Приоритетная + Проверьте своё интернет соединение + Информация о комиссии сети недоступна + Из + Лимит газа + Это максимальное количество газа, которое будет потрачено на выполнение транзакции или контракта. Лимит газа предотвращает неожиданные или неограниченные расходы при выполнении транзакции. + Цена газа + Это стоимость, которую вы готовы заплатить за каждую единицу газа. Чем выше цена газа, тем быстрее ваша транзакция будет обработана. + Всё + Максимальная сумма + Комиссия не превысит + Допустим ввод только цифр + Покрытие сетевой комиссии + Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса + Недостаточно средств + Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе. + Экзистенциальный депозит + Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. + Установлена высокая комиссия + Ввиду особенности сети %1$s комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %2$s. + Комиссия повышена + Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению + Недопустимая сумма + Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %2$s. + Адрес получателя не активирован. \nПожалуйста, измените сумму отправки, чтобы продолжить. + Сумма отправки не может быть менее %s + Оставить %s + Уменьшить на %s + Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции + Возможны задержки по транзакции + Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. + Лимит транзакции + Опционально + Пожалуйста, совместите свой QR-код с квадратом, чтобы отсканировать его. Убедитесь, что вы сканируете адрес в сети %s. + Последние + Получатель + Неверный адрес + Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов. + Отправить + Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. + Мои кошельки + Способ измерения комиссии за биткоин-транзакцию. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый виртуальный байт в транзакции. Чем выше число, тем быстрее будет обработана транзакция майнерами. + Сатоши / вбайт + Отправка + Нажмите на любое поле, чтобы изменить его + Отправка %s + Вы отправляете **%1$s**, включая комиссию сети %2$s + Отправка %s + Всего + %1$s и %2$s будет отправлено + ≈ %1$s (вкл. комиссию: %2$s) + %s будет отправлено + Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время + Неверный адрес + Транзакция отправлена + Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. + Революционный аппаратный кошелек + До трех карт с одним кошельком + Все ключи в безопасности + Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте + Тысячи криптовалют + Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. + Кошелек для каждого + Встречайте Tangem + Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах + Поддержка Web 3.0 + В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя + В сумму включена комиссия провайдера сервиса. + Комиссии + Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен. + Подтвердить + Вы отправляете + Дать разрешение + Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму. + Недостаточно средств + Подтвердить + Текущая транзакция + Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена. + Дать разрешение + Укажите лимит доступа к выбранному токену + Количество %s + Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s + Безлимитно + В процессе + Обменять + Вы получите + Выберите токен + не доступен + Балансы скрыты + Балансы показаны + Отменить + Выбранная операция в данный момент недоступна. Попробуйте позже. + В данный момент покупка монеты %s недоступна. Следите за нашими обновлениями. + У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. + У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. + В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. + Продажа средств станет доступной после завершения транзакции(-ий) в сети %s + Отправка средств станет доступной после завершения транзакции(-ий) в сети %s + В данный момент продажа %s недоступна. Следите за нашими обновлениями. + Сгенерировать XPUB + Скрыть + Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. + Скрыть %s + Скрыть токен + %1$s токен в сети %%image%% %2$s + Токен в сети %%image%% %1$s + Токен %1$s (%2$s) является основной валютой в сети %3$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети + Невозможно скрыть %s + Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля. + Обмен с Changelly, %s комиссии + Обменять + контракт: %s + У вас еще нет транзакций + Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию. + Несколько адресов + История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе. + Операция + от: %s + на: %s + Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d + Вы отсканировали не ту twin-карту. Пожалуйста, попробуйте отсканировать другую + Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька. + Один кошелек. Две карты. + Сканировать карту #%s + Создание кошелька + Отсканируйте twin-карту #%s + Подготовка карты + Tangem Twin + Это действие необратимо. У вас не будет доступа к старому кошельку. + Приложите twin-карту с номером %s и не убирайте до окончания операции + Используйте %s или отсканируйте карту, чтобы получить доступ к своему кошельку + Добавить новый кошелек + Вы уверены, что хотите удалить этот кошелек? + Произошла ошибка, пожалуйста, отсканируйте свою карту для входа + Этот кошелек уже был сохранен, вы можете добавить другой + Кошелек с именем %s уже существует + Имя кошелька + Переименование кошелька + Разблокировать все + Разблокировать все с %s + Блокчейн недоступен. Попробуйте позже. + Отсканируйте карту + Запрос на подпись сообщения.\n\n%s + Dapp %1$s, запрос на\nподпись транзакции с BNB.\n\n%2$s + Торговый ордер на %1$s\nЦена: %2$s\nСумма к получению: %3$s\nСумма к оплате: %4$s + Детали транзакции:\nОт: %1$s\nК: %2$s\nСумма: %3$s + Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код + Запрос на создание транзакции для %1$s\n%2$s\n\nСумма: %3$s\nКомиссия: %4$s\nВсего: %5$s\nБаланс: %6$s + Невозможно отправить транзакцию. Недостаточно средств. + Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. + Не все токены добавлены в ваш список. Пожалуйста, добавьте их в начале, а потом попробуйте снова. Недостающие токены: \n + Не удалось подписать сообщение.\nПожалуйста, попробуйте еще раз + Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже. + Запрос на подключение через WalletConnect содержит неподдерживаемые блокчеины. Неподдерживаемые блокчеины:\n + Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. + Произошла непредвиденная ошибка. Сообщение ошибки: %s Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. + Неверная карта выбрана в приложении Tangem + Не удалось создать транзакцию из данных Dapp. Код: %s + Произошла непредвиденная ошибка. Код ошибки: %d Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. + Нет открытых сессий WalletConnect + Упс. Нет сессий. + Не удалось создать пару WalletConnect: %1$s + Вставить из буфера обмена + Сообщение для %1$s:\n%2$s + Запрос на открытие сессии для\n%1$s\n\nСЕТЬ: %2$s\n\nURL: %3$s + Операция не может быть завершена.\n\nВы уже установили сеанс WalletConnect с этими параметрами. + Сканировать новый код + Эту карту нельзя использовать с WalletConnect. + Сеть не поддерживается. Пожалуйста, выберите другую сеть. + Выберите сеть + Сессии WalletConnect + Подключение к dApps + WalletConnect + Рыночная цена %s + за 24 часа + Сеть %s + Адрес скопирован в буфер обмена + Нет соединения с интернетом + Tangem + Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку + Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян. + Ошибка активации + По решению разработчиков сети BNB, стандарт BEP-2 перестанет поддерживаться в июне 2024 года. Чтобы не потерять свои активы, их необходимо преобразовать в стандарт BEP-20. Используйте функцию обмена в приложении чтобы перевести их в cеть BNB Smart Chain. + Отключение сети BNB Beacon Chain + Можно лучше + Нравится + Понятно! + Очень круто! + Обновить + Вы находитесь в режиме демо + Демо режим включен + Отсканированная вами карта является картой разработчика. Не используйте ее для создания своего кошелька. + Не для пользователя! + Cеть %1$s использует концепцию экзистенциального депозита. Если баланс вашего счета будет ниже %2$s, то он будет деактивирован, а средства на счете уничтожены. + Для работы с сетью необходим депозит + Обмен будет доступен после завершения %s транзакции + У вас есть активная транзакция + Разрешение обмена в процессе и будет скоро завершено + Разрешение в процессе + Минимальная сумма обмена - %1$s. Пожалуйста, убедитесь, что остаток после обмена также не будет меньше %2$s. + У вас в списке нет монет доступных для обмена с %s + Нет доступных для обмена токенов + Чтобы совершить транзакцию, вам необходимо внести немного %1$s %2$s + Невозможно покрыть комиссию %s + Сумма получения не может быть менее %s + Cервис временно недоступен + Сумма для обмена должна быть не более %s + Сумма для обмена должна быть не менее %s + Пожалуйста, измените сумму для обмена + Возможно, данная карта - образец или подделка + Ошибка проверки подлинности + Ассоциировать + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%1$s %2$s + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять + Ассоциируете свой токен + Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена + На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. + Малое количество подписей + Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. + + Используйте вашу карту, чтобы получить адрес для %d сети + Используйте вашу карту, чтобы получить адреса для %d сетей + Используйте вашу карту, чтобы получить адреса для %d сетей + Используйте вашу карту, чтобы получить адреса для %d сетей + + Некоторые адреса отсутствуют + В данный момент сеть недоступна. Пожалуйста, попробуйте позже. + Сеть недоступна + Пополните ваш кошелек + Ваш кошелек не имеет резервной копии. Проведите эту процедуру сейчас, чтобы защитить ваши активы. + Резервная копия отсутствует + Эта карта ранее использовалась для подписи транзакций. Если она получена от ненадежного источника, рассмотрите возможность вывода своих средств. Если это ваша карта, дополнительных действий не требуется. + Карта уже подписывала транзакции + Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше + Нравится Tangem? + Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его + Необходима плата за аренду сети + %1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети. + Недостаточно %1$s для оплаты комиссии сети + Сеть Солана испытывает высокую нагрузку. Если Ваша транзакция не прошла в течение 2 минут, повторите её отправку. + Оповещение сети Солана + Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату. + Некоторые сети в настоящее время недоступны. Пожалуйста, повторите попытку позже. + Некоторые сети недоступны + Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки. + Только для целей тестирования + Отказаться + Вы не закончили резервное копирование. Хотите продолжить? + Да, возобновить + Отказаться + Если сейчас отказаться, то придётся сбрасывать карты до заводских настроек, чтобы начать заново + Возобновить резервное копирование + Это необратимое действие + Войти с %s + Сканировать карту + Используйте %s или отсканируйте карту для входа в приложение + C возвращением! diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index ea0198dfa6..4f7f952256 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1,720 +1,713 @@ - Add custom token - Manage tokens - Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. - Request support - Try again - This feature is disabled in Demo mode - Reason: %s - Can\'t send a transaction - The selected does not support the %1$s network - To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. - Tokens in %1$s network are not supported by this card due to firmware limitation. - Thank you for your feedback. We will respond as soon as possible - Your suggestions were sent - Please try to tap the card exactly as shown in the animation or request support. - Are you having difficulty scanning your card? - This card is not designed to work with this app - Default Fee - Enable Default Fee to set transaction fees automatically and skip the Fee page when sending funds. You can always go back to this page if necessary. - Go to settings to enable biometric authentication in the Tangem App - Enable biometric authentication - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - Removing the saved card deletes all the saved wallets and their access codes from the app. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Dark - Light - System default - If system is selected, the app will auto-adjust based on your device\'s system settings - System - Theme - App Settings - To hide or show your balances, simply flip your device screen down, or switch it off in Settings - Don\'t show again - Got it - Balances are hidden - Please scan the card - Please try again in 30 seconds or scan the card - Too many attempts - You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings. - Start backup process - - %d card - %d cards - - Disable this option if you don\'t want this card to be used to reset access codes on other cards in this wallet. Please note that this will also prevent you from resetting the access code on this card. - Allows you to use this card to reset access code on other cards in this wallet - Access code recovery - Reset - Are you sure you want to do this? - Change Access Code - Access code will be changed on this card only - Reset to Factory Settings - Security Mode - Card Settings - In addition to network fee, the Cardano network charges %1$s ADA when transacting with the %2$s token - Cardano transaction requirements - To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value (5 ADA recommended) - Insufficient ADA for token transfer - You must maintain some ADA because you have some tokens on the Cardano blockchain - Not enough ADA - Accept - Access denied - Apply - Approval - Attention - Balance: %s - Balance - biometric authentication - biometrics - Buy - Go to %1$s - You have not given access to your camera, please adjust your privacy settings - Cancel - Close - Continue - Copy - Copy address - Create - Delete - Disabled - Disconnect - Done - Enable - Enabled - Error - Explore - Explore transaction history - Explorer - Fee - Network fees are charges users pay to process and confirm transactions. The fee amount can be affected by network congestion, transaction size, and execution priority. %s - Custom - Fast - Market - Slow - Speed and fee - Get addresses - Go to provider - Import - Locked - Main network - Network fee - Next - No - No address - OK - Primary Card - Passphrase - Paste - Read more - Receive - Reject - Reload - Rename - Retry - Save changes - Search - Search tokens - Seed phrase - Select action - Sell - Send - The server is not available, please try again later - Share - Sign - Sign and send - Start - Submit - Success - Support - Swap - terms and conditions - Transaction failed - Transactions - Transfer - I understand - There was an error. Please try again. - Unreachable - Warning - Yes - Contract address copied! - Available networks - Add token - Contract address - Contract address is invalid - Please select the network - Decimal must be a valid integer, up to %li - Custom derivation - E. g. m/00\'/0000\'/0\'/0/0 - Enter custom derivation - Decimals - Derivation Path - Default - BIP44 coin type - The derivation path you\'ve entered is not valid - E.g. USD Coin - Name - Not selected - Network - Token network - You can manually add a token that is not natively supported by Tangem - E.g. USDC - Symbol - Token symbol - This token/network has already been added to your list - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - Be aware of adding scam tokens, they can cost nothing - Note that tokens can be created by anyone - Chat - Access code - You will have to submit the correct access code before scanning the card - Long Tap - This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. - Passcode - Before executing any command entailing a change of the card state, you will have to enter the passcode. - Referral program - Flip your device screen down to quickly hide and show balances - %s hashes - Card ID - Contact to Support - Link More Cards - App Currency - Flip-to-Hide Balances - Issuer - Signed - If you forget the code you will lose access to your funds. Code recovery is not possible. - Details - Check your internet connection or switch to a different network - Terms of Service - You have used a card from another wallet. Tap the card associated with this wallet - My tokens - You haven\'t added any tokens yet. Add tokens via Market to swap - Cannot be swapped for %s - Provided by - Status - Tangem offers token swaps via 3rd-party providers according to each provider\'s terms - Choose provider - An error occurred. Code: %s - Oops! Swapping the selected pair through the chosen provider is temporarily unavailable. Please try again later. (Code: %s) - Selected provider is unavailable at the moment. Please try again later. (Code: %s) - Swaps are unavailable at the moment. Please try again later. (Code: %s) - Estimated amount - Exchange by %s - Visit provider’s website to refund your money - Operation failed by provider - Visit provider’s website for verification - KYC verification required by provider - Canceled - Confirmed - Confirming - Confirming... - Exchanged - Exchanging - Exchanging... - Failed - Deposit received - Awaiting deposit - Awaiting deposit... - Refunded - Sending to you - Sending to you... - Sent - Provider-sourced data. Estimated amount subject to change due to market conditions. - Exchange status - Verification required - List of all tokens added to your wallet - Fetching best rates... - Floating rate - By using swap functionality, you agree with provider’s %s - By using swap functionality, you agree with provider’s %1$s and %2$s - More providers are coming soon.\nStay tuned! - Privacy Policy - Provider - Best rate - Available up to %s - Available from %s - Unavailable for this pair - Permission Required - Terms of Use - No tokens found. Please try another request - ID: %s - Transaction ID copied - The following information is optional. You can erase it if you don\'t want to share it. - Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have - Hi support team, - Please tell us more about your issue. Every small detail can help. - My suggestions - Can\'t scan a card - Feedback - Tangem feedback - Can\'t send a transaction - Order card - Scan card - To change the access code tap the card as shown above and do not remove until the end of the operation - To change the passcode tap the card as shown above and do not remove until the end of the operation - To create the wallet tap the card as shown above and do not remove until the end of the operation - Tap to scan - Tap to sign - Tap the card - You have updated biometrics, scan your card to enter - To begin tracking your crypto assets and transactions, add tokens - Manage tokens - To access all the networks you need to scan the card - Scan your card - Enjoy %1$s service fees on swaps via Changelly from February %2$s-%3$s - Swap with Changelly, %s fees - Tokens - Add - Edit - Coin market cap - Blockchain the cryptocurrency was initially created - Native network - Using non-native networks for tokens enables cross-blockchain interoperability, allowing assets to be utilized in diverse decentralized applications and smart contracts across platforms. However, this often involves a custodian or smart contract to hold the original asset securely, introducing centralization and counterparty risk. - Not original or primary blockchain the token is hosted - Non-native networks - Choose networks - Wallet - Couldn’t find this token, you can add it manually - - %1$d of %2$d wallet - %1$d of %2$d wallets - - e.g. BTC I trust, hodl I must - The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it. - Upvote - Choose wallet - The wallet doesn\'t support more than one network - You have to set up a single access code to protect all your cards - Protect - You can set up an individual access code on each card later - Personalize - The access code can be restored with a linked card. Don’t keep all cards at one place. - Restore - Choose any word, phrase, or number you want as your access code - Create Access Code - Enter your access code one more time to avoid a mistake - Re-enter your Access Code - Access code must be at least 4 characters long - Entered access code didn\'t match the initial access code - Please repeat the operation. The card will be reset to factory settings. - Activation error - You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? - The backup process is partly complete. You can\'t exit it now. - The passphrase is an advanced security feature that crypto wallets use. It adds an extra word or phrase of your own choosing to your already existing recovery phrase to unlock a brand-new set of addresses. - Add a backup card - Scan the card #%d - Backup now - Scan the primary card - Continue to my wallet - Finalize the backup - Receive crypto - Scan primary card - Skip for later - How does it work? - Let\'s generate all the keys on your card and create a secure wallet - Create wallet - Create a wallet - Other options - Your keys will be securely generated inside the card. There is no seed phrase, which means nobody can export or steal it. - Generate keys privately - Your card is activated and ready to be used - Success! - In this case, you will need to start from the beginning. - Do you want to exit the activation process? - Getting started - Another wallet has already been created on the card you\'re trying to add. If you have funds in this wallet, please withdraw it and then reset this card and add it as a backup. - Creating a backup - Read more about seed phrase - - - Write these %d words down in the order given below and store them in a safe and secret place. - - Your seed phrase - - - %d words - - To import your wallet, enter your seed phrase in the field below - Generate seed phrase - Import wallet - A seed phrase is a series of words that allows you to recover your wallet. Unlike the keys generated by the card, seed phrases are unprotected and can be copied and stolen. Use this option at your own risk. - Use seed phrase - Invalid seed phrase. Please check the word order. - Invalid seed phrase. Please check your spelling. - Legacy - To check whether you’ve written down your seed phrase correctly, please enter the 2nd, 7th and 11th words - So, let’s check - To start the backup process add up to two backup cards. - You can add one more card or finalize the backup process - Prepare the backup card with number %s - Scan the primary card to start the backup process. - Prepare the primary card with number %s - Your wallet card is configured and ready for use. - Max number of cards added. Finalize the backup process. - Activating card - Backup card #%d - No backup cards - One backup card added - Prepare your card - Two backup cards added - To get started, simply top up the wallet with any amount - To get started, simply top up the wallet with more than %1$s %2$s - Buy crypto - Show the wallet\'s address - Activate a wallet - The twinning process is partly complete. You can\'t exit it now. - If the process of creating the wallet gets interrupted in any way, you\'ll have to start over - You can backup your keys up to two other blank Tangem Wallet cards. - Access code can be restored with one of backup cards. - All the backup cards can be used as full-functional with the identical keys. - You will be able to set an access code to protect your wallets. - Backup wallet - Access code restore - Identical cards - Access code - Group - By balance - Organize tokens - Ungroup - Select from the gallery - Settings - You have not given access to your camera - Camera access denied - Enjoying our app? - %1$s (%2$s) on %3$s network - Send only %s to this address. Sending any other currency will result in its irreversible loss. - Participate - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Error code: %s. Please try again later. - Upcoming payments - Your friends bought - Less - More - No upcoming payments - - for %d wallet - for %d wallets - - Will get ^^%1$s^^ for each wallet bought by your friend on your %2$s network address %3$s ^^30 days after^^ that - You - Will get a - when buying a wallet on tangem.com - %s discount - Your friend - Personal code copied! - Your personal code - Buy Tangem Wallet with discount!\n%s - Refer your friends to Tangem - You\'ve accepted - By tapping this button you accept - of the referral program - - %d wallet - %d wallets - - Reset the Card - I understand that after performing this action, I will no longer have access to the current wallet - I realize that I can\'t use this card to recover my access code on the other cards of the current wallet - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. - Do you have a bank card from another country and a residence permit or registration outside the Russian Federation? - Russian bank cards are not currently accepted - Log into the app and check your balance without scanning the card - Access the app - Allow to use biometrics - Biometrics will be requested instead of the access code for interactions with your wallet - Access code - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable biometric authorization - Would you like to use biometrics? - Note that making a transaction with your funds will still require your card - Scan Card - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Get your card ready! - Already included in the entered address - The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. - You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? - Reason: %1$s\nCode: %2$s - The transaction is not completed - Amount - Base fee - Represents the part of the transaction fee that goes to the miner - %1$s, %2$s - Address - Destination Tag - Enter address - Address is the same as wallet address - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Include fee - Low - Normal - Priority - Check your network connection - Network fee info unreachable - From - Gas limit - This is the maximum amount of gas that will be spent to complete a transaction or contract. A gas limit prevents unexpected or unlimited charges when executing a transaction. - Gas price - This is the cost you are willing to pay for each unit of gas. The higher the gas price, the faster your transaction will be processed. - Max - Maximum amount - Max fee - The fee that will be charged for your transaction. You can set your own value. - Numbers only for Destination Tag - Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level - Network fee coverage - Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance - Total exceeds balance - Leave %s - The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance. - Existential deposit - The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. - Custom fee is high - Due to the peculiarities of the Tezos network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %s. - The fee is higher - The included commission exceeds the transfer amount, leading to a negative value - Invalid amount - The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s. - Target account is not created. Please change the amount to send. - The amount to send must be at least %s - Reduce by %s - Reduce to %s - Kindly be aware that your transaction may experience delays under specific fee settings - Transaction delays are possible - Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount. - Transaction limitation - Optional - Priority fee - Represents the minimum gasUsed multiplier required for a transaction to be included in a block. This is the part of the transaction fee that is burnt. - Please align your QR code with the square to scan it. Ensure you scan %s network address. - Recent - Recipient - Not a valid address - Ensure that you are sending funds to an %s wallet address. Errors may result in the loss of your tokens - Send to - A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds - My wallets - A way of measuring Bitcoin transaction fees. It indicates the number of the smallest Bitcoin unit (Satoshi) for each virtual byte in a transaction. The higher the number, the faster the transaction will be processed by miners. - Satoshi / vByte - Sending... - Tap any field to change it - Send %s - You are sending %1$s including a network fee of %2$s - Sending %s - Total - %1$s and %2$s will be sent - ≈ %1$s (inc. fee: %2$s) - %s will be sent - Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while - Invalid address - %1$s (%2$s) - Transaction sent - Store your crypto assets secure while keeping private keys contained in your card - Revolutionary Hardware Wallet - Up to 3 physical cards to one wallet - Ultra Secure Backup - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - Thousands of Currencies - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - The Wallet for Everyone - Meet Tangem - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - Web 3.0 Compatible - The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address. - The amount includes the service provider\'s fee. - Fees - All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval. - Approve - You swap - Give Permission - Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome. - Insufficient funds - Sending amount will be reduced to cover the selected fee level - Approve - Current transaction - The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. - Give Permission - Specify the approve limit for the selected token - Amount %s - To continue, grant 1inch smart contracts permission to use your %s - Unlimited - In progress - Swap - You receive - Choose token - not available - Balances hidden - Balances shown - Undo - This operation is currently unavailable. Please try again later. - Buying %s is not available at the moment. Please check our updates. - You do not have funds to sell. Top up your account to be able to sell funds from it. - You do not have funds to send. Top up your account to be able to send funds from it. - Swapping %s is not available at the moment. Please check our updates. - Selling funds will be available once the pending transaction(s) in network %s is complete - Sending funds will be available once the pending transaction(s) in network %s is complete - Selling %s is not available at the moment. Please check our updates. - Choose address - Generate XPUB - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Hide %s - Hide token - %1$s token in %%image%% %2$s network - Token in %%image%% %1$s network - The %1$s token is the main currency on the %2$s network and cannot be hidden as long as you have other tokens on this network in the list. - Unable to hide %s - Exchange this token for another at %1$s service fees from February %2$s-%3$s. - Swap with Changelly, %s fees - Swap now - contract: %s - You don\'t have any transactions yet - Failed to load transaction history.\nClick on reload button to update the information. - Multiple addresses - Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer. - Operation - from: %s - to: %s - You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d - You\'ve scanned wrong twin card. Please try another one - This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. - One wallet. Two cards. - Scan the card #%s - Creating wallet - Scan the #%s twin card - Preparing card - Tangem Twin - This action is irreversible. You will not have access to the old wallet. - Tap the twin card with number %s and do not remove until the end of the operation - Use %s or scan a card to have an access to your wallet - Add new wallet - Are you sure you want to delete this wallet? - An error has occurred, please scan your card to log in - This wallet has already been saved, you can add another one - Wallet name - Rename Wallet - Unlock all - Unlock all with %s - Blockchain is unreachable. Try later - Scan the card - Requesting to sign a message.\n\n%s - Dapp %1$s, requesting to\nsign BNB transaction.\n\n%2$s - Trade order for %1$s\nPrice: %2$s\nAmount to receive: %3$s\nAmount to pay: %4$s - Transaction details:\nFrom: %1$s\nTo: %2$s\nAmount: %3$s - Clipboard contain WalletConnect code. Use copied value or scan QR-code - Request to create transaction for %1$s\n%2$s\n\nAmount: %3$s\nFee: %4$s\nTotal: %5$s\nBalance: %6$s - Can\'t send transaction. Not enough funds. - Failed to establish WalletConnect session. Please, try again later. - Not all tokens were added to your list. Please add them first and try again. Missing tokens:\n - Failed to sign message.\nPlease, try again - Failed to establish WalletConnect session: timeout error. Please, try again later. - Session request contains unsupported blockchains for WalletConnect connection. Unsupported blockchains:\n - Connection with this Dapp cannot be established due to its technical implementation. - We\'ve encountered unknown error. Error message: %s. If the problem persists — feel free to contact our support - Wrong card selected in Tangem App - Failed to create transaction from Dapp data. Code: %s - We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support - No opened WalletConnect sessions - Ooops. No Sessions. - Failed to pairing WalletConnect session: %1$s - Paste from clipboard - Message for %1$s:\n%2$s - Request to start a session for\n%1$s\n\nNETWORK: %2$s\n\nURL: %3$s - The operation couldn\'t be completed.\n\nYou have already established a WalletConnect session with this parameters. - Scan new code - This card can\'t be used to establish WalletConnect session - This network is not supported. Please select another network. - Select network - WalletConnect Sessions - Connect to dApps - WalletConnect - %s Market Price - last 24h - %s network - Address was copied to clipboard - No internet connection - Tangem - Use %s or scan a card to unlock access to your wallet - Please withdraw all funds from this wallet, reset it to factory settings, and create a new one. Access to the current wallet will be lost. - Activation error - Could be better - Like it - Ok, Got it! - Really cool! - Refresh - You are currently in the Demo mode - Demo mode active - The card you scanned is a developer card. Do not use it to create your wallet. - Not for users! - %1$s network requires an Existential Deposit. If your account drops below %2$s, it will be deactivated, and any remaining funds will be destroyed. - Network requires Existential Deposit - Swap will be available after the %s transaction is complete - You have active transaction - Swap approval is underway and will be completed shortly - Approval in progress - The minimum swapping amount is %1$s. Please ensure that the remaining balance after the swap will not be less than %2$s. - You do not have any %s exchangeable coins in your list - No available tokens to swap - To make a transaction you need to deposit some %1$s %2$s - Unable to cover %s fee - The amount to receive must be at least %s - Service temporarily unavailable - The amount of tokens to be swapped must not exceed %s - The amount to swap must be at least %s - Please change the amount to swap - This card might be a production sample or counterfeit - Authenticity check failed - Associate - This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s - This token must be associated with your Hedera account before you can receive it - Associate your token - Hot enough %s. Top up your Hedera account to associate this token - Only %s signatures are left on this card. You must withdraw all of your funds. - Low signature count - Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. - - Use your card to get an address for %d network - Use your card to get an addresses for %d networks - - Some addresses are missing - The network is currently unreachable. Please try again later. - Network is unreachable - Top up your wallet - Your wallet hasn\'t been backed up. Carry out this procedure to protect your assets now. - Missing backup - This card has been previously used for transactions. If received from an untrusted source, consider withdrawing all funds. If it\'s your card, no action is required. - Card has already signed transactions - Your review keeps us motivated to make Tangem Wallet even better - Enjoying Tangem? - You must associate your token before receiving tokens - Network rent fee required - %1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. - Insufficient %1$s to cover network fee - Transaction pending - The Solana network is congested. If your transaction is not processed within 2 minutes, please repeat the transaction. - Solana Network Alert - Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free. - Some networks currently are unreachable. Please try again later. - Some networks are unreachable - This is a Testnet card. It cannot process transactions and should only be used for testing and development purposes. - For testing purposes only - Discard - You have an interrupted backup. Do you want to resume? - Yes, resume - Discard - If you will discard the backup now, then you will have to reset the cards to factory settings to start over again - Resume backup - This is an irreversible action - Log in with %s - Scan card - Use %s or scan a card to access the app - Welcome back! + Add custom token + Manage tokens + Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. + Request support + This feature is disabled in Demo mode + Reason: %s + Can\'t send a transaction + The selected does not support the %1$s network + To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. + Tokens in %1$s network are not supported by this card due to firmware limitation. + Are you having difficulty scanning your card? + This card is not designed to work with this app + Default Fee + Enable Default Fee to set transaction fees automatically and skip the Fee page when sending funds. You can always go back to this page if necessary. + Go to settings to enable biometric authentication in the Tangem App + Enable biometric authentication + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + Removing the saved card deletes all the saved wallets and their access codes from the app. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Dark + Light + System default + Theme + App Settings + To hide or show your balances, simply flip your device screen down, or switch it off in Settings + Don\'t show again + Got it + Balances are hidden + Please scan the card + Please try again in 30 seconds or scan the card + Too many attempts + You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings. + Start backup process + + %d card + %d cards + + Disable this option if you don\'t want this card to be used to reset access codes on other cards in this wallet. Please note that this will also prevent you from resetting the access code on this card. + Allows you to use this card to reset access code on other cards in this wallet + Access code recovery + Reset + Are you sure you want to do this? + Change Access Code + Access code will be changed on this card only + Reset to Factory Settings + Security Mode + Card Settings + In addition to network fee, the Cardano network charges %1$s ADA when transacting with the %2$s token + Cardano transaction requirements + To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value (5 ADA recommended) + Insufficient ADA for token transfer + You must maintain some ADA because you have some tokens on the Cardano blockchain + Not enough ADA + Accept + Access denied + Apply + Approval + Attention + Balance: %s + Balance + biometric authentication + biometrics + Buy + Go to %1$s + You have not given access to your camera, please adjust your privacy settings + Cancel + Close + Continue + Copy + Copy address + Create + Delete + Disabled + Done + Enable + Enabled + Error + Explore + Explore transaction history + Explorer + Fee + Network fees are charges users pay to process and confirm transactions. The fee amount can be affected by network congestion, transaction size, and execution priority. %s + Custom + Fast + Market + Slow + Speed and fee + Get addresses + Go to provider + Import + Locked + Main network + Network fee + Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level + Next + No + No address + OK + Primary Card + Passphrase + Paste + Read more + Receive + Reject + Reload + Rename + Save changes + Search + Search tokens + Seed phrase + Select action + Sell + Send + The server is not available, please try again later + Share + Sign + Sign and send + Start + Submit + Success + Support + Swap + terms and conditions + Transaction failed + Transactions + Transfer + I understand + There was an error. Please try again. + Unreachable + Yes + Contract address copied! + Available networks + Add token + Contract address + Contract address is invalid + Please select the network + Decimal must be a valid integer, up to %li + Custom derivation + E. g. m/00\'/0000\'/0\'/0/0 + Enter custom derivation + Decimals + Derivation Path + Default + BIP44 coin type + The derivation path you\'ve entered is not valid + E.g. USD Coin + Name + Not selected + Network + Token network + You can manually add a token that is not natively supported by Tangem + E.g. USDC + Symbol + Token symbol + This token/network has already been added to your list + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + Be aware of adding scam tokens, they can cost nothing + Note that tokens can be created by anyone + Chat + Access code + You will have to submit the correct access code before scanning the card + Long Tap + This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. + Passcode + Before executing any command entailing a change of the card state, you will have to enter the passcode. + Referral program + Flip your device screen down to quickly hide and show balances + %s hashes + Card ID + Contact to Support + Link More Cards + App Currency + Flip-to-Hide Balances + Issuer + Signed + Details + Check your internet connection or switch to a different network + Terms of Service + You have used a card from another wallet. Tap the card associated with this wallet + My tokens + You haven\'t added any tokens yet. Add tokens via Market to swap + Cannot be swapped for %s + Provided by + Status + Tangem offers token swaps via 3rd-party providers according to each provider\'s terms + Choose provider + An error occurred. Code: %s + Oops! Swapping the selected pair through the chosen provider is temporarily unavailable. Please try again later. (Code: %s) + Selected provider is unavailable at the moment. Please try again later. (Code: %s) + Swaps are unavailable at the moment. Please try again later. (Code: %s) + Estimated amount + Exchange by %s + Visit provider’s website to refund your money + Operation failed by provider + Visit provider’s website for verification + KYC verification required by provider + Canceled + Confirmed + Confirming + Confirming... + Exchanged + Exchanging + Exchanging... + Failed + Deposit received + Awaiting deposit + Awaiting deposit... + Refunded + Sending to you + Sending to you... + Sent + Provider-sourced data. Estimated amount subject to change due to market conditions. + Exchange status + Verification required + List of all tokens added to your wallet + Fetching best rates... + Floating rate + By using swap functionality, you agree with provider’s %s + By using swap functionality, you agree with provider’s %1$s and %2$s + More providers are coming soon.\nStay tuned! + Privacy Policy + Provider + Best rate + Available up to %s + Available from %s + Unavailable for this pair + Permission Required + Terms of Use + No tokens found. Please try another request + ID: %s + Transaction ID copied + The following information is optional. You can erase it if you don\'t want to share it. + Tell us what functions you are missing, and we will try to help you. + Please tell us what card do you have + Hi support team, + Please tell us more about your issue. Every small detail can help. + My suggestions + Can\'t scan a card + Feedback + Tangem feedback + Can\'t send a transaction + Order card + Scan card + To change the access code tap the card as shown above and do not remove until the end of the operation + To change the passcode tap the card as shown above and do not remove until the end of the operation + To create the wallet tap the card as shown above and do not remove until the end of the operation + Tap to scan + Tap to sign + Tap the card + You have updated biometrics, scan your card to enter + To begin tracking your crypto assets and transactions, add tokens + Manage tokens + To access all the networks you need to scan the card + Scan your card + Enjoy %1$s service fees on swaps via Changelly from February %2$s-%3$s + Swap with Changelly, %s fees + Tokens + Book now + Save **$50** while booking via our partner Travala: **%1s - %2s** + Book your holidays with Tangem and pay in crypto + Add + Edit + Coin market cap + Blockchain the cryptocurrency was initially created + Native network + Using non-native networks for tokens enables cross-blockchain interoperability, allowing assets to be utilized in diverse decentralized applications and smart contracts across platforms. However, this often involves a custodian or smart contract to hold the original asset securely, introducing centralization and counterparty risk. + Not original or primary blockchain the token is hosted + Non-native networks + Choose networks + Wallet + Couldn’t find this token, you can add it manually + + %1$d of %2$d wallet + %1$d of %2$d wallets + + e.g. BTC I trust, hodl I must + The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it. + Upvote + Choose wallet + The wallet doesn\'t support more than one network + You have to set up a single access code to protect all your cards + Protect + You can set up an individual access code on each card later + Personalize + The access code can be restored with a linked card. Don’t keep all cards at one place. + Restore + Choose any word, phrase, or number you want as your access code + Create Access Code + Enter your access code one more time to avoid a mistake + Re-enter your Access Code + Access code must be at least 4 characters long + Entered access code didn\'t match the initial access code + Please repeat the operation. The card will be reset to factory settings. + Activation error + You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? + The backup process is partly complete. You can\'t exit it now. + The passphrase is an advanced security feature that crypto wallets use. It adds an extra word or phrase of your own choosing to your already existing recovery phrase to unlock a brand-new set of addresses. + Add a backup card + Scan the card #%d + Backup now + Scan the primary card + Continue to my wallet + Finalize the backup + Receive crypto + Scan primary card + Skip for later + How does it work? + Let\'s generate all the keys on your card and create a secure wallet + Create wallet + Create a wallet + Other options + Your keys will be securely generated inside the card. There is no seed phrase, which means nobody can export or steal it. + Generate keys privately + Your card is activated and ready to be used + Success! + In this case, you will need to start from the beginning. + Do you want to exit the activation process? + Getting started + Another wallet has already been created on the card you\'re trying to add. If you have funds in this wallet, please withdraw it and then reset this card and add it as a backup. + Creating a backup + Read more about seed phrase + + + Write these %d words down in the order given below and store them in a safe and secret place. + + Your seed phrase + + + %d words + + To import your wallet, enter your seed phrase in the field below + Generate seed phrase + Import wallet + A seed phrase is a series of words that allows you to recover your wallet. Unlike the keys generated by the card, seed phrases are unprotected and can be copied and stolen. Use this option at your own risk. + Use seed phrase + Invalid seed phrase. Please check the word order. + Invalid seed phrase. Please check your spelling. + Legacy + To check whether you’ve written down your seed phrase correctly, please enter the 2nd, 7th and 11th words + So, let’s check + To start the backup process add up to two backup cards. + You can add one more card or finalize the backup process + Prepare the backup card with number %s + Scan the primary card to start the backup process. + Prepare the primary card with number %s + Your wallet card is configured and ready for use. + Max number of cards added. Finalize the backup process. + Activating card + Backup card #%d + No backup cards + One backup card added + Prepare your card + Two backup cards added + To get started, simply top up the wallet with any amount + To get started, simply top up the wallet with more than %1$s %2$s + Buy crypto + Show the wallet\'s address + Activate a wallet + The twinning process is partly complete. You can\'t exit it now. + If the process of creating the wallet gets interrupted in any way, you\'ll have to start over + You can backup your keys up to two other blank Tangem Wallet cards. + Access code can be restored with one of backup cards. + All the backup cards can be used as full-functional with the identical keys. + You will be able to set an access code to protect your wallets. + Backup wallet + Access code restore + Identical cards + Access code + Group + By balance + Organize tokens + Ungroup + Select from the gallery + Settings + You have not given access to your camera + Camera access denied + Enjoying our app? + %1$s (%2$s) on %3$s network + Send only %s to this address. Sending any other currency will result in its irreversible loss. + Participate + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Error code: %s. Please try again later. + Upcoming payments + Your friends bought + Less + More + No upcoming payments + + for %d wallet + for %d wallets + + Will get ^^%1$s^^ for each wallet bought by your friend on your %2$s network address %3$s ^^30 days after^^ that + You + Will get a + when buying a wallet on tangem.com + %s discount + Your friend + Personal code copied! + Your personal code + Buy Tangem Wallet with discount!\n%s + Refer your friends to Tangem + You\'ve accepted + By tapping this button you accept + of the referral program + + %d wallet + %d wallets + + Reset the Card + I understand that after performing this action, I will no longer have access to the current wallet + I realize that I can\'t use this card to recover my access code on the other cards of the current wallet + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Do you have a bank card from another country and a residence permit or registration outside the Russian Federation? + Russian bank cards are not currently accepted + Log into the app and check your balance without scanning the card + Access the app + Allow to use biometrics + Biometrics will be requested instead of the access code for interactions with your wallet + Access code + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable biometric authorization + Would you like to use biometrics? + Note that making a transaction with your funds will still require your card + Scan Card + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Get your card ready! + Already included in the entered address + The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. + You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? + Reason: %1$s\nCode: %2$s + The transaction is not completed + Amount + Base fee + Represents the part of the transaction fee that goes to the miner + You can set your transaction fee by adjusting the value in the Satoshi per vByte field. + %1$s, %2$s + Address + Destination Tag + Enter address + Address is the same as wallet address + The fee that will be charged for your transaction. You can set your own value. + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Include fee + Low + Normal + Priority + Check your network connection + Network fee info unreachable + From + Gas limit + This is the maximum amount of gas that will be spent to complete a transaction or contract. A gas limit prevents unexpected or unlimited charges when executing a transaction. + Gas price + This is the cost you are willing to pay for each unit of gas. The higher the gas price, the faster your transaction will be processed. + Max + Maximum amount + Max fee + Numbers only for Destination Tag + Network fee coverage + Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance + Total exceeds balance + The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance. + Existential deposit + The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. + Custom fee is high + Due to the peculiarities of the %1$s network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %2$s. + The fee is higher + The included commission exceeds the transfer amount, leading to a negative value + Invalid amount + The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s. + Target account is not created. Please change the amount to send. + The amount to send must be at least %s + Leave %s + Reduce by %s + Kindly be aware that your transaction may experience delays under specific fee settings + Transaction delays are possible + Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount. + Transaction limitation + Optional + Priority fee + Represents the minimum gasUsed multiplier required for a transaction to be included in a block. This is the part of the transaction fee that is burnt. + Please align your QR code with the square to scan it. Ensure you scan %s network address. + Recent + Recipient + Not a valid address + Ensure the receiving wallet address is on the %s network to avoid losing your tokens + Send to + A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds + My wallets + A way of measuring Bitcoin transaction fees. It indicates the number of the smallest Bitcoin unit (Satoshi) for each virtual byte in a transaction. The higher the number, the faster the transaction will be processed by miners. + Satoshi / vByte + Sending... + Tap any field to change it + Send %s + You are sending **%1$s** including a network fee of %2$s + Sending %s + Total + %1$s and %2$s will be sent + ≈ %1$s (inc. fee: %2$s) + %s will be sent + Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while + Invalid address + %1$s (%2$s) + Transaction sent + Store your crypto assets secure while keeping private keys contained in your card + Revolutionary Hardware Wallet + Up to 3 physical cards to one wallet + Ultra Secure Backup + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + Thousands of Currencies + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + The Wallet for Everyone + Meet Tangem + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + Web 3.0 Compatible + The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address. + The amount includes the service provider\'s fee. + Fees + All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval. + Approve + You swap + Give Permission + Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome. + Insufficient funds + Approve + Current transaction + The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. + Give Permission + Specify the approve limit for the selected token + Amount %s + To continue, grant 1inch smart contracts permission to use your %s + Unlimited + In progress + Swap + You receive + Choose token + not available + Balances hidden + Balances shown + Undo + This operation is currently unavailable. Please try again later. + Buying %s is not available at the moment. Please check our updates. + You do not have funds to sell. Top up your account to be able to sell funds from it. + You do not have funds to send. Top up your account to be able to send funds from it. + Swapping %s is not available at the moment. Please check our updates. + Selling funds will be available once the pending transaction(s) in network %s is complete + Sending funds will be available once the pending transaction(s) in network %s is complete + Selling %s is not available at the moment. Please check our updates. + Generate XPUB + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Hide %s + Hide token + %1$s token in %%image%% %2$s network + Token in %%image%% %1$s network + The %1$s (%2$s) token is the main currency on the %3$s network and cannot be hidden as long as you have other tokens on this network in the list + Unable to hide %s + Exchange this token for another at %1$s service fees from February %2$s-%3$s. + Swap with Changelly, %s fees + Swap now + contract: %s + You don\'t have any transactions yet + Failed to load transaction history.\nClick on reload button to update the information. + Multiple addresses + Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer. + Operation + from: %s + to: %s + You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d + You\'ve scanned wrong twin card. Please try another one + This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. + One wallet. Two cards. + Scan the card #%s + Creating wallet + Scan the #%s twin card + Preparing card + Tangem Twin + This action is irreversible. You will not have access to the old wallet. + Tap the twin card with number %s and do not remove until the end of the operation + Use %s or scan a card to have an access to your wallet + Add new wallet + Are you sure you want to delete this wallet? + An error has occurred, please scan your card to log in + This wallet has already been saved, you can add another one + The wallet with name %s already exists + Wallet name + Rename Wallet + Unlock all + Unlock all with %s + Blockchain is unreachable. Try later + Scan the card + Requesting to sign a message.\n\n%s + Dapp %1$s, requesting to\nsign BNB transaction.\n\n%2$s + Trade order for %1$s\nPrice: %2$s\nAmount to receive: %3$s\nAmount to pay: %4$s + Transaction details:\nFrom: %1$s\nTo: %2$s\nAmount: %3$s + Clipboard contain WalletConnect code. Use copied value or scan QR-code + Request to create transaction for %1$s\n%2$s\n\nAmount: %3$s\nFee: %4$s\nTotal: %5$s\nBalance: %6$s + Can\'t send transaction. Not enough funds. + Failed to establish WalletConnect session. Please, try again later. + Not all tokens were added to your list. Please add them first and try again. Missing tokens:\n + Failed to sign message.\nPlease, try again + Failed to establish WalletConnect session: timeout error. Please, try again later. + Session request contains unsupported blockchains for WalletConnect connection. Unsupported blockchains:\n + Connection with this Dapp cannot be established due to its technical implementation. + We\'ve encountered unknown error. Error message: %s. If the problem persists — feel free to contact our support + Wrong card selected in Tangem App + Failed to create transaction from Dapp data. Code: %s + We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support + No opened WalletConnect sessions + Ooops. No Sessions. + Failed to pairing WalletConnect session: %1$s + Paste from clipboard + Message for %1$s:\n%2$s + Request to start a session for\n%1$s\n\nNETWORK: %2$s\n\nURL: %3$s + The operation couldn\'t be completed.\n\nYou have already established a WalletConnect session with this parameters. + Scan new code + This card can\'t be used to establish WalletConnect session + This network is not supported. Please select another network. + Select network + WalletConnect Sessions + Connect to dApps + WalletConnect + %s Market Price + last 24h + %s network + Address was copied to clipboard + No internet connection + Tangem + Use %s or scan a card to unlock access to your wallet + Please withdraw all funds from this wallet, reset it to factory settings, and create a new one. Access to the current wallet will be lost. + Activation error + According to BNB network developers, support for the BEP-2 standard will end in June 2024. To avoid losing assets with this standard, please convert them to the BEP-20 standard. Use our swap service to transfer them to the BNB Smart Chain network. + BNB Beacon Chain will shut down + Could be better + Like it + Ok, Got it! + Really cool! + Refresh + You are currently in the Demo mode + Demo mode active + The card you scanned is a developer card. Do not use it to create your wallet. + Not for users! + %1$s network requires an Existential Deposit. If your account drops below %2$s, it will be deactivated, and any remaining funds will be destroyed. + Network requires Existential Deposit + Swap will be available after the %s transaction is complete + You have active transaction + Swap approval is underway and will be completed shortly + Approval in progress + The minimum swapping amount is %1$s. Please ensure that the remaining balance after the swap will not be less than %2$s. + You do not have any %s exchangeable coins in your list + No available tokens to swap + To make a transaction you need to deposit some %1$s %2$s + Unable to cover %s fee + The amount to receive must be at least %s + Service temporarily unavailable + The amount of tokens to be swapped must not exceed %s + The amount to swap must be at least %s + Please change the amount to swap + This card might be a production sample or counterfeit + Authenticity check failed + Associate + This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s + This token must be associated with your Hedera account before you can receive it + Associate your token + Not enough %s. Top up your Hedera account to associate this token + Only %s signatures are left on this card. You must withdraw all of your funds. + Low signature count + Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. + + Use your card to get an address for %d network + Use your card to get an addresses for %d networks + + Some addresses are missing + The network is currently unreachable. Please try again later. + Network is unreachable + Top up your wallet + Your wallet hasn\'t been backed up. Carry out this procedure to protect your assets now. + Missing backup + This card has been previously used for transactions. If received from an untrusted source, consider withdrawing all funds. If it\'s your card, no action is required. + Card has already signed transactions + Your review keeps us motivated to make Tangem Wallet even better + Enjoying Tangem? + You must associate your token before receiving tokens + Network rent fee required + %1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. + Insufficient %1$s to cover network fee + The Solana network is congested. If your transaction is not processed within 2 minutes, please repeat the transaction. + Solana Network Alert + Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free. + Some networks currently are unreachable. Please try again later. + Some networks are unreachable + This is a Testnet card. It cannot process transactions and should only be used for testing and development purposes. + For testing purposes only + Discard + You have an interrupted backup. Do you want to resume? + Yes, resume + Discard + If you will discard the backup now, then you will have to reset the cards to factory settings to start over again + Resume backup + This is an irreversible action + Log in with %s + Scan card + Use %s or scan a card to access the app + Welcome back! diff --git a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt index a1613d958b..4f6bf8e17b 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt @@ -3,14 +3,10 @@ package com.tangem.data.settings.di import com.tangem.data.settings.DefaultAppRatingRepository import com.tangem.data.settings.DefaultSettingsRepository import com.tangem.data.settings.DefaultPromoSettingsRepository -import com.tangem.data.source.preferences.PreferencesDataSource -import com.tangem.data.settings.DefaultSwapPromoRepository import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.settings.repositories.AppRatingRepository import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.repositories.PromoSettingsRepository -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.domain.settings.repositories.SwapPromoRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 26e4ec6069..55ede01473 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -315,7 +315,6 @@ internal class StateBuilder( private fun getWarningsForSuccessState( quoteModel: SwapState.QuotesLoadedState, fromToken: CryptoCurrency, - ignoreAmountReduce: Boolean, selectedFeeType: FeeType, ): List { val warnings = mutableListOf() diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index 1c192640f7..853191b4a2 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -172,14 +172,6 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { subtitle = resourceReference(id = R.string.no_account_send_to_create), ) - class HasPendingTransactions(val coinSymbol: String) : Informational( - title = resourceReference(R.string.warning_send_blocked_pending_transactions_title), - subtitle = resourceReference( - id = R.string.warning_send_blocked_pending_transactions_message, - formatArgs = wrappedList(coinSymbol), - ), - ) - data class NetworkShutdown(private val title: TextReference, private val subtitle: TextReference) : Warning( title = title, subtitle = subtitle, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index bb8d8c2c6b..8f368bb1b3 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -4,7 +4,6 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.extensions.resourceReference -import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 1da8d9fff1..7c24809756 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -87,7 +87,7 @@ markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-628" +tangemBlockchainSdk = "develop-633" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-354" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 9cc58e92b588437738738a5bf2d6cc62438b4356 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 May 2024 16:04:24 +0300 Subject: [PATCH 69/98] Updated on 2026-08-14 --- .../java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt | 1 + .../java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index 2f5e52b496..eb9e2a434c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -10,6 +10,7 @@ import retrofit2.http.* * [REDACTED_AUTHOR] */ +@Suppress("TooManyFunctions") interface TangemTechApi { @GET("coins") diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt index a1f077948d..4045a3a98a 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt @@ -4,6 +4,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWallet import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull + /** * Use case for getting list of user wallets * From 7e8b240f982185a55fe6a8f15c3abe5a391e0a61 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 May 2024 17:13:32 +0800 Subject: [PATCH 70/98] Updated on 2026-08-14 --- .../validators/ContractAddressValidator.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidator.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidator.kt index 5db91d6354..6bb0c880af 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidator.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidator.kt @@ -23,12 +23,12 @@ object ContractAddressValidator { private fun validateAddress(blockchain: Blockchain, address: String): Boolean { return when (blockchain) { - Blockchain.Unknown, Blockchain.Binance, Blockchain.BinanceTestnet -> { - SuccessAddressValidator.validate(address) - } - else -> { - blockchain.validateAddress(address) - } + Blockchain.Unknown, + Blockchain.Binance, + Blockchain.BinanceTestnet, + Blockchain.Cardano, + -> SuccessAddressValidator.validate(address) + else -> blockchain.validateAddress(address) } } From ce2567f096d7984c4d6c215122806ed5966dd19f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 May 2024 16:27:47 +0800 Subject: [PATCH 71/98] Updated on 2026-08-14 --- .../components/snackbar/CopiedTextSnackbar.kt | 114 ++++++++++++++++++ .../snackbar/CopiedTextSnackbarHost.kt | 25 ++++ .../ui/components/snackbar/TangemSnackbar.kt | 84 +++++++++++++ .../components/snackbar/TangemSnackbarHost.kt | 48 ++++++++ .../snackbar/TangemSnackbarModelProvider.kt | 74 ++++++++++++ 5 files changed, 345 insertions(+) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbar.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbarHost.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbar.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarHost.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarModelProvider.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbar.kt b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbar.kt new file mode 100644 index 0000000000..35cc8f6903 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbar.kt @@ -0,0 +1,114 @@ +package com.tangem.core.ui.components.snackbar + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Icon +import androidx.compose.material3.SnackbarData +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme + +/** + * Snackbar to inform the user about copying text to the clipboard + * + * @param message message + * @param modifier modifier + * + * @see Figma + * +[REDACTED_AUTHOR] + */ +@Composable +fun CopiedTextSnackbar(message: TextReference, modifier: Modifier = Modifier) { + BaseSnackbar(message = message, modifier = modifier) +} + +/** + * Snackbar to inform the user about copying text to the clipboard. + * + * @param snackbarData this is needed to better support Material3.SnackbarHost, but only supports the message field + * @param modifier modifier + * + * @see Figma + * +[REDACTED_AUTHOR] + */ +@Composable +fun CopiedTextSnackbar(snackbarData: SnackbarData, modifier: Modifier = Modifier) { + BaseSnackbar(message = stringReference(snackbarData.visuals.message), modifier = modifier) +} + +@Composable +private fun BaseSnackbar(message: TextReference, modifier: Modifier = Modifier) { + Row( + modifier = modifier + .background(color = TangemTheme.colors.icon.secondary, shape = TangemTheme.shapes.roundedCorners8) + .heightIn(min = TangemTheme.dimens.size48) + .padding( + horizontal = TangemTheme.dimens.spacing16, + vertical = TangemTheme.dimens.spacing14, + ), + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + verticalAlignment = Alignment.CenterVertically, + ) { + MarkIcon() + + MessageText(text = message, modifier = Modifier.weight(weight = 1f, fill = false)) + } +} + +@Composable +private fun MarkIcon() { + Icon( + painter = painterResource(id = R.drawable.ic_check_24), + contentDescription = null, + modifier = Modifier.size(TangemTheme.dimens.size20), + tint = TangemTheme.colors.icon.accent, + ) +} + +@Composable +private fun MessageText(text: TextReference, modifier: Modifier = Modifier) { + Text( + text = text.resolveReference(), + modifier = modifier, + color = TangemTheme.colors.text.disabled, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = TangemTheme.typography.body2, + ) +} + +@Preview(widthDp = 344, showBackground = true, fontScale = 1f) +@Preview(widthDp = 344, showBackground = true, fontScale = 1f, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview(widthDp = 344, showBackground = true, fontScale = 2f) +@Composable +private fun Preview_CopiedTextSnackbar( + @PreviewParameter(CopiedTextSnackbarDataProvider::class) message: TextReference, +) { + TangemTheme(isDark = false) { + CopiedTextSnackbar(message = message) + } +} + +private class CopiedTextSnackbarDataProvider : CollectionPreviewParameterProvider( + collection = listOf( + stringReference(value = "Copied!"), + stringReference(value = "Contract address copied!"), + stringReference(value = "Coooooooooooontract addreeeeeeeeeeeeeeeess coooooooooooooooopied!"), + ), +) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbarHost.kt b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbarHost.kt new file mode 100644 index 0000000000..3cff3dd5c0 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbarHost.kt @@ -0,0 +1,25 @@ +package com.tangem.core.ui.components.snackbar + +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +/** + * SnackbarHost to inform the user about copying text to the clipboard + * Based on Material3 component. It's best way to show [CopiedTextSnackbar]. + * + * @param hostState snackbar host state + * @param modifier modifier + * + * @see Figma + * +[REDACTED_AUTHOR] + */ +@Composable +fun CopiedTextSnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier) { + SnackbarHost(hostState = hostState, modifier = modifier) { + CopiedTextSnackbar(snackbarData = it) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbar.kt b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbar.kt new file mode 100644 index 0000000000..70cacc3a82 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbar.kt @@ -0,0 +1,84 @@ +package com.tangem.core.ui.components.snackbar + +import android.content.res.Configuration +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview + +/** + * Tangem snackbar. + * Based on Material3 component. It can be presented as one line or multi lines snackbar – depends on text length. + * + * @param data snackbar data + * @param modifier modifier + * @param actionOnNewLine flag that indicates if the action should be displayed on a new line (default: false) + * + * @see Figma + * +[REDACTED_AUTHOR] + */ +@Composable +fun TangemSnackbar(data: SnackbarData, modifier: Modifier = Modifier, actionOnNewLine: Boolean = false) { + Snackbar( + modifier = modifier, + action = { + ActionButton(label = data.visuals.actionLabel, onClick = data::performAction) + }, + actionOnNewLine = actionOnNewLine, + shape = TangemTheme.shapes.roundedCorners8, + containerColor = TangemTheme.colors.icon.secondary, + ) { + MessageText(text = data.visuals.message) + } +} + +@Composable +private fun ActionButton(label: String?, onClick: () -> Unit) { + if (!label.isNullOrBlank()) { + TextButton( + onClick = onClick, + colors = ButtonDefaults.textButtonColors( + contentColor = TangemTheme.colors.text.primary2, + ), + content = { + Text( + text = label, + maxLines = 1, + style = TangemTheme.typography.button, + ) + }, + ) + } +} + +@Composable +private fun MessageText(text: String) { + Text( + text = text, + color = TangemTheme.colors.text.disabled, + textAlign = TextAlign.Start, + overflow = TextOverflow.Ellipsis, + style = TangemTheme.typography.body2, + ) +} + +/** + * IMPORTANT! + * Preview doesn't work correctly, check on device or start [TangemSnackbarHost]'s preview in interactive mode * + */ +@Preview(widthDp = 344, showBackground = true, fontScale = 1f) +@Preview(widthDp = 344, showBackground = true, fontScale = 1f, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview(widthDp = 344, showBackground = true, fontScale = 2f) +@Composable +private fun Preview_TangemSnackbar(@PreviewParameter(TangemSnackbarModelProvider::class) model: TangemSnackbarModel) { + TangemThemePreview { + TangemSnackbar(data = model.snackbarData, actionOnNewLine = model.actionOnNewLine) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarHost.kt b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarHost.kt new file mode 100644 index 0000000000..d1cd229f6d --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarHost.kt @@ -0,0 +1,48 @@ +package com.tangem.core.ui.components.snackbar + +import android.content.res.Configuration +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import com.tangem.core.ui.res.TangemThemePreview + +/** + * Tangem snackbar host. + * Based on Material3 component. It's best way to show [TangemSnackbar]. + * + * @param hostState snackbar host state + * @param modifier modifier + * @param actionOnNewLine flag that indicates if the action should be displayed on a new line (default: false) + * + * @see Figma + * +[REDACTED_AUTHOR] + */ +@Composable +fun TangemSnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier, actionOnNewLine: Boolean = false) { + SnackbarHost(hostState = hostState, modifier = modifier) { data -> + TangemSnackbar(data = data, actionOnNewLine = actionOnNewLine) + } +} + +@Preview(widthDp = 344, showBackground = true, fontScale = 1f) +@Preview(widthDp = 344, showBackground = true, fontScale = 1f, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview(widthDp = 344, showBackground = true, fontScale = 2f) +@Composable +private fun Preview_TangemSnackbar(@PreviewParameter(TangemSnackbarModelProvider::class) model: TangemSnackbarModel) { + TangemThemePreview { + val snackbarHostState = remember(::SnackbarHostState) + + TangemSnackbarHost(hostState = snackbarHostState, actionOnNewLine = model.actionOnNewLine) + + LaunchedEffect(key1 = null) { + snackbarHostState.showSnackbar(visuals = model.snackbarData.visuals) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarModelProvider.kt b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarModelProvider.kt new file mode 100644 index 0000000000..e2bdbf3ecf --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarModelProvider.kt @@ -0,0 +1,74 @@ +package com.tangem.core.ui.components.snackbar + +import androidx.compose.material3.SnackbarData +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarVisuals +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider + +internal data class TangemSnackbarModel(val snackbarData: SnackbarData, val actionOnNewLine: Boolean) + +internal class TangemSnackbarModelProvider : CollectionPreviewParameterProvider( + listOf( + createTangemSnackbarModel( + message = "Single-line description.", + actionLabel = "Button", + actionOnNewLine = false, + ), + createTangemSnackbarModel( + message = "Very loooooooooooong single-line description.", + actionLabel = "Button", + actionOnNewLine = false, + ), + createTangemSnackbarModel( + message = "Single-line description.", + actionLabel = "Very looooooong button name", + actionOnNewLine = false, + ), + createTangemSnackbarModel( + message = "Single-line description.", + actionLabel = "Button", + actionOnNewLine = true, + ), + createTangemSnackbarModel( + message = "Very loooooooooooong single-line description.", + actionLabel = "Button", + actionOnNewLine = true, + ), + createTangemSnackbarModel( + message = "Single-line description.", + actionLabel = "Very looooooong button name", + actionOnNewLine = true, + ), + ), +) { + + companion object { + + fun createTangemSnackbarModel( + message: String, + actionLabel: String, + actionOnNewLine: Boolean, + ): TangemSnackbarModel { + return TangemSnackbarModel( + snackbarData = createSnackbarData(message, actionLabel), + actionOnNewLine = actionOnNewLine, + ) + } + + private fun createSnackbarData(message: String, actionLabel: String): SnackbarData { + return object : SnackbarData { + + override val visuals: SnackbarVisuals + get() = object : SnackbarVisuals { + override val message: String = message + override val actionLabel: String = actionLabel + override val duration: SnackbarDuration = SnackbarDuration.Short // Never-mind + override val withDismissAction: Boolean = false // Never-mind + } + + override fun dismiss() = Unit + override fun performAction() = Unit + } + } + } +} \ No newline at end of file From 0b7c992ec882d249c24f5273fe637e92d41fa2fe Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 May 2024 13:05:17 +0500 Subject: [PATCH 72/98] Updated on 2026-08-14 --- .../clipboard/DefaultClipboardManager.kt | 12 ++++++++ .../common/clipboard/MockClipboardManager.kt | 11 +++++++ .../tap/common/haptic/DefaultHapticManager.kt | 5 ++++ .../tangem/tap/di/ClipboardManagerModule.kt | 30 +++++++++++++++++++ .../core/ui/clipboard/ClipboardManager.kt | 14 +++++++++ .../buttons/actions/ActionButtonConfig.kt | 2 ++ .../ui/components/buttons/actions/Actions.kt | 21 +++++++++++-- .../tangem/core/ui/haptic/HapticManager.kt | 2 ++ .../core/ui/haptic/MockHapticManager.kt | 4 +++ .../tokendetails/TokenDetailsPreviewData.kt | 2 +- .../components/TokenDetailsActionButton.kt | 10 ++++++- .../TokenDetailsActionButtonsConverter.kt | 1 + .../TokenDetailsSkeletonStateConverter.kt | 2 +- .../viewmodels/TokenDetailsClickIntents.kt | 5 +++- .../viewmodels/TokenDetailsViewModel.kt | 20 ++++++++++++- 15 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt create mode 100644 app/src/main/java/com/tangem/tap/common/clipboard/MockClipboardManager.kt create mode 100644 app/src/main/java/com/tangem/tap/di/ClipboardManagerModule.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/clipboard/ClipboardManager.kt diff --git a/app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt b/app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt new file mode 100644 index 0000000000..8b7ee07933 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.common.clipboard + +import android.content.ClipData +import com.tangem.core.ui.clipboard.ClipboardManager +import android.content.ClipboardManager as AndroidClipboardManager + +internal class DefaultClipboardManager(private val clipboardManager: AndroidClipboardManager) : ClipboardManager { + + override fun setText(label: String, text: String) { + clipboardManager.setPrimaryClip(ClipData.newPlainText(label, text)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/clipboard/MockClipboardManager.kt b/app/src/main/java/com/tangem/tap/common/clipboard/MockClipboardManager.kt new file mode 100644 index 0000000000..bdab75088f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/clipboard/MockClipboardManager.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.common.clipboard + +import com.tangem.core.ui.clipboard.ClipboardManager +import timber.log.Timber + +internal object MockClipboardManager : ClipboardManager { + override fun setText(label: String, text: String) { + /** Intentionnaly do nothing */ + Timber.w("Clipboard Manager not available") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/haptic/DefaultHapticManager.kt b/app/src/main/java/com/tangem/tap/common/haptic/DefaultHapticManager.kt index 8afeba9a48..688dea7f67 100644 --- a/app/src/main/java/com/tangem/tap/common/haptic/DefaultHapticManager.kt +++ b/app/src/main/java/com/tangem/tap/common/haptic/DefaultHapticManager.kt @@ -11,6 +11,10 @@ class DefaultHapticManager(private val vibrator: Vibrator) : HapticManager { vibrate(VIBRATION_SHORT_DURATION) } + override fun vibrateMeduim() { + vibrate(VIBRATION_MEDIUM_LOW_DURATION) + } + override fun vibrateLong() { vibrate(VIBRATION_MEDIUM_DURATION) } @@ -25,6 +29,7 @@ class DefaultHapticManager(private val vibrator: Vibrator) : HapticManager { companion object { const val VIBRATION_SHORT_DURATION = 50L + const val VIBRATION_MEDIUM_LOW_DURATION = 75L const val VIBRATION_MEDIUM_DURATION = 100L const val VIBRATION_LONG_DURATION = 200L } diff --git a/app/src/main/java/com/tangem/tap/di/ClipboardManagerModule.kt b/app/src/main/java/com/tangem/tap/di/ClipboardManagerModule.kt new file mode 100644 index 0000000000..052f1c4541 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/ClipboardManagerModule.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.di + +import android.content.Context +import com.tangem.core.ui.clipboard.ClipboardManager +import com.tangem.tap.common.clipboard.MockClipboardManager +import com.tangem.tap.common.clipboard.DefaultClipboardManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton +import android.content.ClipboardManager as AndroidClipboardManager + +@Module +@InstallIn(SingletonComponent::class) +internal class ClipboardManagerModule { + + @Provides + @Singleton + fun provideClipboardManager(@ApplicationContext context: Context): ClipboardManager { + val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as? AndroidClipboardManager + + return if (clipboardManager != null) { + DefaultClipboardManager(clipboardManager = clipboardManager) + } else { + MockClipboardManager + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/clipboard/ClipboardManager.kt b/core/ui/src/main/java/com/tangem/core/ui/clipboard/ClipboardManager.kt new file mode 100644 index 0000000000..2e6354a2e2 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/clipboard/ClipboardManager.kt @@ -0,0 +1,14 @@ +package com.tangem.core.ui.clipboard + +import androidx.compose.runtime.Stable + +/** + * Ready to use clipboard manager. + * Does not require context to work + */ +@Stable +interface ClipboardManager { + + /** Copies to clipboard [text] with optional [label] */ + fun setText(label: String = "", text: String) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt index 6b9347623a..c93d92643e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt @@ -9,6 +9,7 @@ import com.tangem.core.ui.extensions.TextReference * @property text text * @property iconResId icon resource id * @property onClick lambda be invoked when action component is clicked + * @property onLongClick lambda be invoked when action component is long clicked * @property enabled enabled * @property dimContent determines whether the button content will be dimmed. This property will be ignored if [enabled] * is `false`. @@ -19,6 +20,7 @@ data class ActionButtonConfig( val text: TextReference, @DrawableRes val iconResId: Int, val onClick: () -> Unit, + val onLongClick: (() -> TextReference?)? = null, val enabled: Boolean = true, val dimContent: Boolean = false, ) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt index 6c7e926dae..60bc6e7e12 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt @@ -1,9 +1,11 @@ package com.tangem.core.ui.components.buttons.actions import android.content.res.Configuration +import android.widget.Toast import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Icon @@ -14,6 +16,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -74,6 +77,7 @@ fun ActionButton( ) } +@OptIn(ExperimentalFoundationApi::class) @Composable private fun Button( config: ActionButtonConfig, @@ -85,13 +89,24 @@ private fun Button( targetValue = if (config.enabled) color else TangemTheme.colors.button.disabled, label = "Update background color", ) - + val context = LocalContext.current Row( modifier = modifier .heightIn(min = TangemTheme.dimens.size36) .clip(shape) .background(color = backgroundColor) - .clickable(enabled = config.enabled, onClick = config.onClick) + .combinedClickable( + enabled = config.enabled, + onClick = config.onClick, + onLongClick = { + val toastReference = config.onLongClick?.invoke() + toastReference?.let { + Toast + .makeText(context, toastReference.resolveReference(context.resources), Toast.LENGTH_SHORT) + .show() + } + }, + ) .padding(start = TangemTheme.dimens.spacing16, end = TangemTheme.dimens.spacing24) .padding(vertical = TangemTheme.dimens.spacing8), horizontalArrangement = Arrangement.Center, diff --git a/core/ui/src/main/java/com/tangem/core/ui/haptic/HapticManager.kt b/core/ui/src/main/java/com/tangem/core/ui/haptic/HapticManager.kt index c4fa2906f4..b93c08bd26 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/haptic/HapticManager.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/haptic/HapticManager.kt @@ -7,5 +7,7 @@ interface HapticManager { fun vibrateShort() + fun vibrateMeduim() + fun vibrateLong() } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/haptic/MockHapticManager.kt b/core/ui/src/main/java/com/tangem/core/ui/haptic/MockHapticManager.kt index 475402f21e..876ee7a05d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/haptic/MockHapticManager.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/haptic/MockHapticManager.kt @@ -6,6 +6,10 @@ object MockHapticManager : HapticManager { /** Intentionnaly do nothing */ } + override fun vibrateMeduim() { + /** Intentionnaly do nothing */ + } + override fun vibrateLong() { /** Intentionnaly do nothing */ } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 9e5149bf68..b16fe4ac2c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -89,7 +89,7 @@ internal object TokenDetailsPreviewData { private val actionButtons = persistentListOf( TokenDetailsActionButton.Buy(dimContent = false, onClick = {}), TokenDetailsActionButton.Send(dimContent = false, onClick = {}), - TokenDetailsActionButton.Receive(onClick = {}), + TokenDetailsActionButton.Receive(onClick = {}, onLongClick = null), TokenDetailsActionButton.Swap(dimContent = false, onClick = {}), ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt index 85a9c70cf8..c5b6078527 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt @@ -11,6 +11,9 @@ internal sealed class TokenDetailsActionButton(val config: ActionButtonConfig) { /** Lambda be invoked when manage button is clicked */ abstract val onClick: () -> Unit + /** Lambda be invoked when manage button is long clicked */ + open val onLongClick: (() -> TextReference?)? = null + /** * Buy * @@ -44,12 +47,17 @@ internal sealed class TokenDetailsActionButton(val config: ActionButtonConfig) { /** * Receive * @property onClick lambda be invoked when Receive button is clicked + * @property onLongClick lambda be invoked when Receive button is long clicked */ - data class Receive(override val onClick: () -> Unit) : TokenDetailsActionButton( + data class Receive( + override val onClick: () -> Unit, + override val onLongClick: (() -> TextReference?)?, + ) : TokenDetailsActionButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_receive), iconResId = R.drawable.ic_arrow_down_24, onClick = onClick, + onLongClick = onLongClick, enabled = true, ), ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt index 24f7f0c2a0..dbf8a99f3c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt @@ -35,6 +35,7 @@ internal class TokenDetailsActionButtonsConverter( is TokenActionsState.ActionState.Receive -> { TokenDetailsActionButton.Receive( onClick = { clickIntents.onReceiveClick(action.unavailabilityReason) }, + onLongClick = clickIntents::onCopyAddress, ) } is TokenActionsState.ActionState.Sell -> { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt index 575565370e..073b8e88ba 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt @@ -90,7 +90,7 @@ internal class TokenDetailsSkeletonStateConverter( return persistentListOf( TokenDetailsActionButton.Buy(dimContent = false, onClick = {}), TokenDetailsActionButton.Send(dimContent = false, onClick = {}), - TokenDetailsActionButton.Receive(onClick = {}), + TokenDetailsActionButton.Receive(onClick = {}, onLongClick = null), TokenDetailsActionButton.Sell(dimContent = false, onClick = {}), TokenDetailsActionButton.Swap(dimContent = false, onClick = {}), ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt index 8b290061ed..54c81902ca 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt @@ -1,8 +1,9 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel -import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason +import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason @Suppress("TooManyFunctions") interface TokenDetailsClickIntents { @@ -50,4 +51,6 @@ interface TokenDetailsClickIntents { fun onSwapPromoClick() fun onGenerateExtendedKey() + + fun onCopyAddress(): TextReference? } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 26d69cf20d..c95d1c7ab7 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -10,9 +10,13 @@ import com.tangem.blockchain.common.address.AddressType import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.deeplink.global.BuyCurrencyDeepLink +import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel +import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels import com.tangem.core.ui.components.transactions.state.TxHistoryState +import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.haptic.HapticManager import com.tangem.datasource.local.swaptx.SwapTransactionStatusStore import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency @@ -25,10 +29,10 @@ import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo -import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkAddress +import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.models.analytics.TokenExchangeAnalyticsEvent import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent @@ -59,6 +63,7 @@ import com.tangem.utils.Provider import com.tangem.utils.coroutines.* import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.* @@ -96,6 +101,8 @@ internal class TokenDetailsViewModel @Inject constructor( private val isDemoCardUseCase: IsDemoCardUseCase, private val reduxStateHolder: ReduxStateHolder, private val analyticsEventsHandler: AnalyticsEventHandler, + private val hapticManager: HapticManager, + private val clipboardManager: ClipboardManager, featureToggles: TokenDetailsFeatureToggles, deepLinksRegistry: DeepLinksRegistry, savedStateHandle: SavedStateHandle, @@ -695,6 +702,17 @@ internal class TokenDetailsViewModel @Inject constructor( onSwapClick(ScenarioUnavailabilityReason.None) } + override fun onCopyAddress(): TextReference? { + val networkAddress = cryptoCurrencyStatus?.value?.networkAddress ?: return null + val addresses = networkAddress.availableAddresses.mapToAddressModels(cryptoCurrency).toImmutableList() + val defaultAddress = addresses.firstOrNull()?.value ?: return null + + hapticManager.vibrateMeduim() + clipboardManager.setText(text = defaultAddress) + analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ButtonCopyAddress(cryptoCurrency.symbol)) + return resourceReference(R.string.wallet_notification_address_copied) + } + private fun handleUnavailabilityReason(unavailabilityReason: ScenarioUnavailabilityReason): Boolean { if (unavailabilityReason == ScenarioUnavailabilityReason.None) return false From 9868dd2bc2a5e34f70f18e6b3c2f38a5add46dda Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 May 2024 14:14:44 +0200 Subject: [PATCH 73/98] Updated on 2026-08-14 --- app/build.gradle.kts | 2 + .../configs/feature_toggles_config.json | 4 ++ features/staking/api/.gitignore | 1 + features/staking/api/build.gradle.kts | 16 +++++ .../featuretoggles/StakingFeatureToggles.kt | 10 +++ .../staking/api/navigation/StakingRouter.kt | 8 +++ features/staking/impl/.gitignore | 1 + features/staking/impl/build.gradle.kts | 62 +++++++++++++++++++ .../impl/di/StakingFeatureTogglesModule.kt | 24 +++++++ .../staking/impl/di/StakingRouterModule.kt | 23 +++++++ .../DefaultStakingFeatureToggles.kt | 17 +++++ .../impl/navigation/DefaultStakingRouter.kt | 8 +++ .../impl/navigation/InnerStakingRouter.kt | 5 ++ settings.gradle.kts | 3 + 14 files changed, 184 insertions(+) create mode 100644 features/staking/api/.gitignore create mode 100644 features/staking/api/build.gradle.kts create mode 100644 features/staking/api/src/main/kotlin/com/tangem/features/staking/api/featuretoggles/StakingFeatureToggles.kt create mode 100644 features/staking/api/src/main/kotlin/com/tangem/features/staking/api/navigation/StakingRouter.kt create mode 100644 features/staking/impl/.gitignore create mode 100644 features/staking/impl/build.gradle.kts create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingFeatureTogglesModule.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingRouterModule.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/featuretoggles/DefaultStakingFeatureToggles.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/navigation/DefaultStakingRouter.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/navigation/InnerStakingRouter.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f775c46b33..46ab6491ef 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -121,6 +121,8 @@ dependencies { implementation(projects.features.send.impl) implementation(projects.features.qrScanning.api) implementation(projects.features.qrScanning.impl) + implementation(projects.features.staking.api) + implementation(projects.features.staking.impl) /** AndroidX libraries */ implementation(deps.androidx.core.ktx) diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index d91a3988e8..7b611616d8 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -30,5 +30,9 @@ { "name": "CARDANO_TOKENS_SUPPORT_ENABLED", "version": "5.11.0" + }, + { + "name": "STAKING_ENABLED", + "version": "undefined" } ] diff --git a/features/staking/api/.gitignore b/features/staking/api/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/features/staking/api/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/staking/api/build.gradle.kts b/features/staking/api/build.gradle.kts new file mode 100644 index 0000000000..a14e2d42b0 --- /dev/null +++ b/features/staking/api/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + id("kotlin-parcelize") + id("configuration") +} + +android { + namespace = "com.tangem.features.staking.api" +} + +dependencies { + + /** AndroidX */ + implementation(deps.androidx.fragment.ktx) +} \ No newline at end of file diff --git a/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/featuretoggles/StakingFeatureToggles.kt b/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/featuretoggles/StakingFeatureToggles.kt new file mode 100644 index 0000000000..90ccdc617d --- /dev/null +++ b/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/featuretoggles/StakingFeatureToggles.kt @@ -0,0 +1,10 @@ +package com.tangem.features.staking.api.featuretoggles + +/** + * Staking feature toggles + */ +interface StakingFeatureToggles { + + /** Availability of staking */ + val isStakingEnabled: Boolean +} \ No newline at end of file diff --git a/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/navigation/StakingRouter.kt b/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/navigation/StakingRouter.kt new file mode 100644 index 0000000000..d2b216513e --- /dev/null +++ b/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/navigation/StakingRouter.kt @@ -0,0 +1,8 @@ +package com.tangem.features.staking.api.navigation + +import androidx.fragment.app.Fragment + +interface StakingRouter { + + fun getEntryFragment(): Fragment +} \ No newline at end of file diff --git a/features/staking/impl/.gitignore b/features/staking/impl/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/staking/impl/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/staking/impl/build.gradle.kts b/features/staking/impl/build.gradle.kts new file mode 100644 index 0000000000..ac2ce74301 --- /dev/null +++ b/features/staking/impl/build.gradle.kts @@ -0,0 +1,62 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.hilt.android) + id("configuration") +} + +android { + namespace = "com.tangem.features.staking.impl" +} + +dependencies { + /** AndroidX */ + implementation(deps.androidx.fragment.ktx) + implementation(deps.androidx.appCompat) + implementation(deps.androidx.paging.runtime) + + /** Other dependencies */ + implementation(deps.kotlin.immutable.collections) + implementation(deps.material) + implementation(deps.arrow.core) + implementation(deps.lifecycle.compose) + implementation(deps.jodatime) + implementation(deps.timber) + + /** Compose */ + implementation(deps.compose.accompanist.systemUiController) + implementation(deps.compose.material3) + implementation(deps.compose.material) + implementation(deps.compose.foundation) + implementation(deps.compose.ui) + implementation(deps.compose.ui.tooling) + implementation(deps.compose.navigation) + implementation(deps.compose.navigation.hilt) + implementation(deps.compose.constraintLayout) + + /** Tangem SDKs */ + implementation(deps.tangem.card.core) + implementation(deps.tangem.blockchain) + + /** Core modules */ + implementation(projects.core.featuretoggles) + implementation(projects.core.ui) + implementation(projects.core.utils) + implementation(projects.core.navigation) + implementation(projects.core.analytics) + implementation(projects.core.analytics.models) + + /** Common */ + implementation(projects.common) + + /** Libs */ + implementation(projects.libs.crypto) + + /** Feature modules */ + implementation(projects.features.staking.api) + + /** DI */ + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingFeatureTogglesModule.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingFeatureTogglesModule.kt new file mode 100644 index 0000000000..e40072f8bf --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingFeatureTogglesModule.kt @@ -0,0 +1,24 @@ +package com.tangem.features.staking.impl.di + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles +import com.tangem.features.staking.impl.featuretoggles.DefaultStakingFeatureToggles +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +/** + * DI module provides implementation of [StakingFeatureToggles] + */ +@Module +@InstallIn(SingletonComponent::class) +internal object StakingFeatureTogglesModule { + + @Provides + @Singleton + fun provideStakingFeatureToggles(featureTogglesManager: FeatureTogglesManager): StakingFeatureToggles { + return DefaultStakingFeatureToggles(featureTogglesManager = featureTogglesManager) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingRouterModule.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingRouterModule.kt new file mode 100644 index 0000000000..c351d87a6e --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingRouterModule.kt @@ -0,0 +1,23 @@ +package com.tangem.features.staking.impl.di + +import com.tangem.features.staking.impl.navigation.DefaultStakingRouter +import com.tangem.features.staking.api.navigation.StakingRouter +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.android.scopes.ActivityScoped + +/** + * DI module provides implementation of [StakingRouter] + */ +@Module +@InstallIn(ActivityComponent::class) +internal object StakingRouterModule { + + @Provides + @ActivityScoped + fun provideStakingRouter(): StakingRouter { + return DefaultStakingRouter() + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/featuretoggles/DefaultStakingFeatureToggles.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/featuretoggles/DefaultStakingFeatureToggles.kt new file mode 100644 index 0000000000..735c22b027 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/featuretoggles/DefaultStakingFeatureToggles.kt @@ -0,0 +1,17 @@ +package com.tangem.features.staking.impl.featuretoggles + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles + +/** + * Default implementation of staking feature toggles + * + * @property featureTogglesManager manager for getting information about the availability of feature toggles + */ +internal class DefaultStakingFeatureToggles( + private val featureTogglesManager: FeatureTogglesManager, +) : StakingFeatureToggles { + + override val isStakingEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled(name = "STAKING_ENABLED") +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/navigation/DefaultStakingRouter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/navigation/DefaultStakingRouter.kt new file mode 100644 index 0000000000..83cd830e4d --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/navigation/DefaultStakingRouter.kt @@ -0,0 +1,8 @@ +package com.tangem.features.staking.impl.navigation + +import androidx.fragment.app.Fragment + +internal class DefaultStakingRouter : InnerStakingRouter { + + override fun getEntryFragment(): Fragment = TODO() +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/navigation/InnerStakingRouter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/navigation/InnerStakingRouter.kt new file mode 100644 index 0000000000..7d7aa226d4 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/navigation/InnerStakingRouter.kt @@ -0,0 +1,5 @@ +package com.tangem.features.staking.impl.navigation + +import com.tangem.features.staking.api.navigation.StakingRouter + +interface InnerStakingRouter : StakingRouter \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index e297edfac7..b714ecb4b4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -153,6 +153,9 @@ include(":features:manage-tokens:impl") include(":features:qr-scanning:api") include(":features:qr-scanning:impl") + +include(":features:staking:api") +include(":features:staking:impl") // endregion Feature modules // region Domain modules From dae3c63448b6e9902280d9cbff885077adc37702 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 May 2024 11:31:47 +0500 Subject: [PATCH 74/98] Updated on 2026-08-14 --- .../core/ui/utils/BigDecimalFormatter.kt | 45 ++++++++++++++++++- .../presentation/ui/SendNavigationButtons.kt | 23 +++++----- .../impl/presentation/utils/FormatterUtils.kt | 31 +++---------- .../TokenDetailsLoadedBalanceConverter.kt | 2 +- .../SingleWalletMarketPriceConverter.kt | 2 +- .../converter/TokenItemStateConverter.kt | 2 +- 6 files changed, 64 insertions(+), 41 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt index b724559114..f995dee52e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt @@ -16,6 +16,10 @@ object BigDecimalFormatter { private const val TEMP_CURRENCY_CODE = "USD" + private val FIAT_FORMAT_THRESHOLD = BigDecimal("0.01") + private const val FIAT_MARKET_DEFAULT_DIGITS = 2 + private const val FIAT_MARKET_EXTENDED_DIGITS = 6 + fun formatCryptoAmount( cryptoAmount: BigDecimal?, cryptoCurrency: String, @@ -82,8 +86,43 @@ object BigDecimalFormatter { val formatterCurrency = getCurrency(fiatCurrencyCode) val formatter = NumberFormat.getCurrencyInstance(locale).apply { currency = formatterCurrency - maximumFractionDigits = 2 - minimumFractionDigits = 2 + maximumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS + minimumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS + roundingMode = RoundingMode.HALF_UP + } + + return if (fiatAmount.isLessThanThreshold()) { + buildString { + append(CAN_BE_LOWER_SIGN) + append( + formatter.format(FIAT_FORMAT_THRESHOLD) + .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol), + ) + } + } else { + formatter.format(fiatAmount) + .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol) + } + } + + fun formatFiatAmountUncapped( + fiatAmount: BigDecimal?, + fiatCurrencyCode: String, + fiatCurrencySymbol: String, + locale: Locale = Locale.getDefault(), + ): String { + if (fiatAmount == null) return EMPTY_BALANCE_SIGN + val formatterCurrency = getCurrency(fiatCurrencyCode) + + val digits = if (fiatAmount.isLessThanThreshold()) { + FIAT_MARKET_EXTENDED_DIGITS + } else { + FIAT_MARKET_DEFAULT_DIGITS + } + val formatter = NumberFormat.getCurrencyInstance(locale).apply { + currency = formatterCurrency + maximumFractionDigits = digits + minimumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS roundingMode = RoundingMode.HALF_UP } @@ -141,4 +180,6 @@ object BigDecimalFormatter { } } } + + private fun BigDecimal.isLessThanThreshold() = this > BigDecimal.ZERO && this < FIAT_FORMAT_THRESHOLD } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index 93cc9067fd..929231d36d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -4,7 +4,10 @@ import androidx.compose.animation.* import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -31,10 +34,10 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.shareText import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.SendUiStateType -import com.tangem.features.send.impl.presentation.utils.getFiatFormatted @Composable internal fun SendNavigationButtons( @@ -175,15 +178,15 @@ private fun SendingText( } if (feeFiat != null && sendingFiat != null) { - val sendingValue = getFiatFormatted( - value = sendingFiat, - currencySymbol = feeState.appCurrency.symbol, - currencyCode = feeState.appCurrency.code, + val sendingValue = BigDecimalFormatter.formatFiatAmount( + fiatAmount = sendingFiat, + fiatCurrencySymbol = feeState.appCurrency.symbol, + fiatCurrencyCode = feeState.appCurrency.code, ) - val feeValue = getFiatFormatted( - value = feeState.fee?.amount?.value, - currencySymbol = feeState.appCurrency.symbol, - currencyCode = feeState.appCurrency.code, + val feeValue = BigDecimalFormatter.formatFiatAmount( + fiatAmount = feeState.fee?.amount?.value, + fiatCurrencySymbol = feeState.appCurrency.symbol, + fiatCurrencyCode = feeState.appCurrency.code, ) val textResource = remember(sendingValue, feeValue) { resourceReference( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt index f2be7e6ed2..7c8a2becb6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt @@ -8,11 +8,8 @@ import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.BigDecimalFormatter.EMPTY_BALANCE_SIGN import com.tangem.domain.appcurrency.model.AppCurrency import java.math.BigDecimal -import java.math.RoundingMode -private const val FIAT_DECIMALS = 2 private const val CRYPTO_FEE_DECIMALS = 6 -private const val FEE_MINIMUM_VALUE = 0.01 internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): TextReference? { if (amount == null) return null @@ -37,27 +34,9 @@ internal fun getFiatReference(value: BigDecimal?, rate: BigDecimal?, appCurrency internal fun getFiatString(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): String { if (value == null || rate == null) return EMPTY_BALANCE_SIGN val feeValue = value.multiply(rate) - return getFiatFormatted(feeValue, appCurrency.code, appCurrency.symbol) -} - -internal fun getFiatFormatted(value: BigDecimal?, currencyCode: String, currencySymbol: String): String { - val scaled = value?.setScale(FIAT_DECIMALS, RoundingMode.UP) ?: BigDecimal.ZERO - return if (scaled < BigDecimal(FEE_MINIMUM_VALUE)) { - buildString { - append(BigDecimalFormatter.CAN_BE_LOWER_SIGN) - append( - BigDecimalFormatter.formatFiatAmount( - fiatAmount = BigDecimal(FEE_MINIMUM_VALUE), - fiatCurrencyCode = currencyCode, - fiatCurrencySymbol = currencySymbol, - ), - ) - } - } else { - BigDecimalFormatter.formatFiatAmount( - fiatAmount = value, - fiatCurrencyCode = currencyCode, - fiatCurrencySymbol = currencySymbol, - ) - } + return BigDecimalFormatter.formatFiatAmount( + fiatAmount = feeValue, + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 05221db9ec..63d735d3ad 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -133,7 +133,7 @@ internal class TokenDetailsLoadedBalanceConverter( private fun formatPrice(status: CryptoCurrencyStatus.Value, appCurrency: AppCurrency): String { val fiatRate = status.fiatRate ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN - return BigDecimalFormatter.formatFiatAmount( + return BigDecimalFormatter.formatFiatAmountUncapped( fiatAmount = fiatRate, fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt index 34579188d3..8d710656dc 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt @@ -47,7 +47,7 @@ internal class SingleWalletMarketPriceConverter( private fun formatPrice(status: CryptoCurrencyStatus.Value, appCurrency: AppCurrency): String { val fiatRate = status.fiatRate ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN - return BigDecimalFormatter.formatFiatAmount( + return BigDecimalFormatter.formatFiatAmountUncapped( fiatAmount = fiatRate, fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt index aa168db655..f41d4d4ee7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt @@ -108,7 +108,7 @@ internal class TokenItemStateConverter( private fun BigDecimal.getFormattedCryptoPrice(): String { val appCurrency = appCurrencyProvider() - return BigDecimalFormatter.formatFiatAmount( + return BigDecimalFormatter.formatFiatAmountUncapped( fiatAmount = this, fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol, From a3295ea1814d6b8aa0df079d1d36df8fbbabe897 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 May 2024 18:53:42 +0300 Subject: [PATCH 75/98] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 6 +- .../BiometricUserWalletsListManager.kt | 15 ++-- .../GeneralUserWalletsListManager.kt | 71 ++++++++++++------- .../RuntimeUserWalletsListManager.kt | 8 +-- .../tap/domain/userWalletList/utils/Mapper.kt | 12 +++- .../configs/feature_toggles_config.json | 2 +- .../wallets/legacy/UserWalletsListManager.kt | 10 +-- .../UserWalletsListManagerExtensions.kt | 2 +- .../wallets/usecase/DeleteWalletUseCase.kt | 21 +++--- .../wallet/viewmodels/WalletViewModel.kt | 17 ++--- .../viewmodels/WalletsUpdateActionResolver.kt | 35 ++------- .../intents/WalletCardClickIntents.kt | 31 +++++--- 12 files changed, 123 insertions(+), 107 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 94171ca9e9..83dd77c841 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -41,7 +41,6 @@ import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.legacy.asLockable import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.features.managetokens.navigation.ManageTokensUi @@ -440,10 +439,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) { - val canSaveWallets = runCatching { userWalletsListManager.asLockable()?.isLockedSync } - .fold(onSuccess = { true }, onFailure = { false }) - - if (canSaveWallets && userWalletsListManager.hasUserWallets) { + if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) { store.dispatch( NavigationAction.NavigateTo( screen = AppScreen.Welcome, diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 99418a7aa0..96419972cb 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -12,6 +12,7 @@ import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository import com.tangem.tap.domain.userWalletList.utils.encryptionKey +import com.tangem.tap.domain.userWalletList.utils.lockAll import com.tangem.tap.domain.userWalletList.utils.toUserWallets import com.tangem.tap.domain.userWalletList.utils.updateWith import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -27,6 +28,8 @@ internal class BiometricUserWalletsListManager( ) : UserWalletsListManager.Lockable { private val state = MutableStateFlow(State()) + override val isLockable: Boolean = true + override val userWallets: Flow> get() = state .mapLatest { it.userWallets } @@ -78,11 +81,13 @@ internal class BiometricUserWalletsListManager( } override fun lock() { - state.update { State() } - } - - override fun isLockable(): Boolean { - return true + state.update { prevState -> + prevState.copy( + encryptionKeys = emptyList(), + userWallets = prevState.userWallets.lockAll(), + isLocked = true, + ) + } } override suspend fun select(userWalletId: UserWalletId): CompletionResult = catching { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt index 762ba82f54..abec2ac6e8 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -33,31 +33,49 @@ internal class GeneralUserWalletsListManager( ) : UserWalletsListManager.Lockable { private val applicationScope = CoroutineScope(dispatchers.io) - private val implementation = MutableStateFlow(runtimeUserWalletsListManager) + private val implementation: MutableStateFlow = MutableStateFlow(value = null) + + private val requireImplementation: UserWalletsListManager + get() = requireNotNull(implementation.value) { + "UserWalletsListManager is not initialized" + } init { subscribeOnCurrentManager() } + override val isLockable: Boolean + get() = requireImplementation.isLockable + override val userWallets: Flow> - get() = implementation.flatMapLatest { it.userWallets } + get() = implementation.transformLatest { impl -> + if (impl != null && impl.hasUserWallets) { + emitAll(impl.userWallets) + } + } override val selectedUserWallet: Flow - get() = implementation.flatMapLatest { it.selectedUserWallet } + get() = implementation.transformLatest { impl -> + if (impl != null && impl.hasUserWallets) { + emitAll(impl.selectedUserWallet) + } + } override val selectedUserWalletSync: UserWallet? - get() = implementation.value.selectedUserWalletSync + get() = requireImplementation.selectedUserWalletSync override val hasUserWallets: Boolean - get() = implementation.value.hasUserWallets + get() = requireImplementation.hasUserWallets override val walletsCount: Int - get() = implementation.value.walletsCount + get() = requireImplementation.walletsCount override val isLocked: Flow - get() = implementation.flatMapLatest { - if (it is UserWalletsListManager.Lockable) { - it.isLocked + get() = implementation.transformLatest { impl -> + if (impl == null) return@transformLatest + + if (impl is UserWalletsListManager.Lockable) { + emitAll(impl.isLocked) } else { error("RuntimeUserWalletsListManager is not lockable") } @@ -65,43 +83,45 @@ internal class GeneralUserWalletsListManager( override val isLockedSync: Boolean get() { - val implementation = implementation.value - return if (implementation is UserWalletsListManager.Lockable) { - implementation.isLockedSync + val impl = requireImplementation + + return if (impl is UserWalletsListManager.Lockable) { + impl.isLockedSync } else { error("RuntimeUserWalletsListManager is not lockable") } } override suspend fun select(userWalletId: UserWalletId): CompletionResult { - return implementation.value.select(userWalletId) + return requireImplementation.select(userWalletId) } override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { - return implementation.value.save(userWallet, canOverride) + return requireImplementation.save(userWallet, canOverride) } override suspend fun update( userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet, ): CompletionResult { - return implementation.value.update(userWalletId, update) + return requireImplementation.update(userWalletId, update) } override suspend fun delete(userWalletIds: List): CompletionResult { - return implementation.value.delete(userWalletIds) + return requireImplementation.delete(userWalletIds) } override suspend fun clear(): CompletionResult { - return implementation.value.clear() + return requireImplementation.clear() } override suspend fun get(userWalletId: UserWalletId): CompletionResult { - return implementation.value.get(userWalletId) + return requireImplementation.get(userWalletId) } override suspend fun unlock(type: UserWalletsListManager.Lockable.UnlockType): CompletionResult { - val implementation = implementation.value + val implementation = requireImplementation + return if (implementation is UserWalletsListManager.Lockable) { implementation.unlock(type) } else { @@ -110,7 +130,8 @@ internal class GeneralUserWalletsListManager( } override fun lock() { - val implementation = implementation.value + val implementation = requireImplementation + return if (implementation is UserWalletsListManager.Lockable) { implementation.lock() } else { @@ -118,10 +139,6 @@ internal class GeneralUserWalletsListManager( } } - override fun isLockable(): Boolean { - return implementation.value.isLockable() - } - private fun subscribeOnCurrentManager() { appPreferencesStore.get(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false) .distinctUntilChanged() @@ -144,17 +161,17 @@ internal class GeneralUserWalletsListManager( destinationManager = possibleManager, ) - previousManager.clear() + previousManager?.clear() } .flowOn(dispatchers.io) .launchIn(applicationScope) } private suspend fun copySelectedUserWallet( - sourceManager: UserWalletsListManager, + sourceManager: UserWalletsListManager?, destinationManager: UserWalletsListManager, ): UserWalletsListManager { - sourceManager.selectedUserWalletSync?.let { selectedWallet -> + sourceManager?.selectedUserWalletSync?.let { selectedWallet -> destinationManager.save(selectedWallet, canOverride = true) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt index 31ef3366dd..ca9a5bd68e 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt @@ -13,6 +13,8 @@ import kotlinx.coroutines.flow.* internal class RuntimeUserWalletsListManager : UserWalletsListManager { private val state = MutableStateFlow(State()) + override val isLockable: Boolean = false + override val userWallets: Flow> get() = state .mapLatest { listOfNotNull(it.userWallet) } @@ -34,7 +36,7 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager { * only 1 wallet stored in runtime implementation */ override val walletsCount: Int - get() = 1 + get() = if (hasUserWallets) 1 else 0 override suspend fun select(userWalletId: UserWalletId): CompletionResult = catching { state.value.userWallet @@ -85,10 +87,6 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager { state.value.userWallet ?: walletNotFound() } - override fun isLockable(): Boolean { - return false - } - private fun saveInternal(userWallet: UserWallet): CompletionResult = catching { state.update { prevState -> prevState.copy( diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt index 451e717e9e..7b869eb69f 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt @@ -61,4 +61,14 @@ internal fun List.updateWith( ?: wallet } } -} \ No newline at end of file +} + +internal fun List.lockAll(): List = map(UserWallet::lock) + +internal fun UserWallet.lock(): UserWallet = copy( + scanResponse = scanResponse.copy( + card = scanResponse.card.copy( + wallets = emptyList(), + ), + ), +) \ No newline at end of file diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 7b611616d8..2bb2538503 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -25,7 +25,7 @@ }, { "name": "TOKEN_LIST_LCE_ENABLED", - "version": "5.10.0" + "version": "5.11.0" }, { "name": "CARDANO_TOKENS_SUPPORT_ENABLED", diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt index 213d780d07..e51ef51dc7 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt @@ -7,6 +7,11 @@ import kotlinx.coroutines.flow.Flow interface UserWalletsListManager { + /** + * Indicates that the [UserWalletsListManager] is [UserWalletsListManager.Lockable] + * */ + val isLockable: Boolean + /** [Flow] with all saved [UserWallet]s updates */ val userWallets: Flow> @@ -84,11 +89,6 @@ interface UserWalletsListManager { */ suspend fun get(userWalletId: UserWalletId): CompletionResult - /** - * Indicates that the [UserWalletsListManager] supports [UserWalletsListManager.Lockable] - * */ - fun isLockable(): Boolean - interface Lockable : UserWalletsListManager { /** diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt index a6bdbde07f..9570eaa90d 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt @@ -49,7 +49,7 @@ suspend fun UserWalletsListManager.unlockIfLockable(type: UnlockType = UnlockTyp * [UserWalletsListManager.Lockable] otherwise * */ fun UserWalletsListManager.asLockable(): UserWalletsListManager.Lockable? { - if (this.isLockable()) { + if (this.isLockable) { return this as? UserWalletsListManager.Lockable } return null diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt index 59084184cc..b4db11932f 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt @@ -1,17 +1,14 @@ package com.tangem.domain.wallets.usecase import arrow.core.Either -import arrow.core.left import arrow.core.raise.either -import arrow.core.right import com.tangem.common.doOnFailure -import com.tangem.common.doOnSuccess import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.DeleteWalletError import com.tangem.domain.wallets.models.UserWalletId /** - * Use case for updating user wallet + * Use case for deleting user wallet * * @property userWalletsListManager user wallets list manager * @@ -19,13 +16,21 @@ import com.tangem.domain.wallets.models.UserWalletId */ class DeleteWalletUseCase(private val userWalletsListManager: UserWalletsListManager) { - suspend operator fun invoke(userWalletId: UserWalletId): Either { + /** + * Deletes user wallet with provided ID. + * + * @param userWalletId ID of user wallet to be deleted. + * + * @return [Either] with [DeleteWalletError] or [Boolean] which indicates that there are still saved wallets. + * */ + suspend operator fun invoke(userWalletId: UserWalletId): Either { return either { userWalletsListManager.delete(userWalletIds = listOf(userWalletId)) - .doOnSuccess { return Unit.right() } - .doOnFailure { return DeleteWalletError.UnableToDelete.left() } + .doOnFailure { + raise(DeleteWalletError.UnableToDelete) + } - return Unit.right() + userWalletsListManager.hasUserWallets } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 775bb89a39..d7865401e0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.navigation.AppScreen import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.settings.CanUseBiometryUseCase import com.tangem.domain.settings.IsWalletsScrollPreviewEnabled @@ -32,10 +31,11 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.* +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") @@ -197,9 +197,9 @@ internal class WalletViewModel @Inject constructor( is WalletsUpdateActionResolver.Action.UpdateWalletName -> { stateHolder.update(transformer = RenameWalletTransformer(action.selectedWalletId, action.name)) } - is WalletsUpdateActionResolver.Action.NoAccessibleWallets -> closeScreen(screen = AppScreen.Welcome) - is WalletsUpdateActionResolver.Action.NoWallets -> closeScreen(screen = AppScreen.Home) - is WalletsUpdateActionResolver.Action.Unknown -> Unit + is WalletsUpdateActionResolver.Action.Unknown -> { + Timber.w("Unable to perfom action: $action") + } } } @@ -313,13 +313,6 @@ internal class WalletViewModel @Inject constructor( ) } - private fun closeScreen(screen: AppScreen) { - if (!screenLifecycleProvider.isBackgroundState.value) { - stateHolder.clear() - router.popBackStack(screen = screen) - } - } - private fun scrollToWallet(index: Int, onConsume: () -> Unit = {}) { stateHolder.update( ScrollToWalletTransformer( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt index 94b310fd4a..737fb1a107 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels +import arrow.core.getOrElse import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase @@ -23,16 +24,14 @@ internal class WalletsUpdateActionResolver @Inject constructor( ) { fun resolve(wallets: List, currentState: WalletScreenState): Action { - val selectedWallet = wallets.getSelectedWallet() + val selectedWallet = getSelectedWalletSyncUseCase().getOrElse { + error("Unable to find selected wallet: $it") + } - val action = if (selectedWallet == null) { - createNoSelectedWalletAction(wallets) + val action = if (isFirstInitialization(currentState)) { + createInitializeWalletsAction(wallets, selectedWallet) } else { - if (isFirstInitialization(currentState)) { - createInitializeWalletsAction(wallets, selectedWallet) - } else { - getUpdateContentAction(currentState, wallets, selectedWallet) - } + getUpdateContentAction(currentState, wallets, selectedWallet) } Timber.d("Resolved action: $action") @@ -40,22 +39,6 @@ internal class WalletsUpdateActionResolver @Inject constructor( return action } - private fun List.getSelectedWallet(): UserWallet? { - return when { - isEmpty() -> null - size == 1 -> if (first().isLocked) null else first() - else -> getSelectedWalletSyncUseCase().fold(ifLeft = { null }, ifRight = { it }) - } - } - - private fun createNoSelectedWalletAction(wallets: List): Action { - return when { - wallets.isEmpty() -> Action.NoWallets - wallets.all(UserWallet::isLocked) -> Action.NoAccessibleWallets - else -> Action.Unknown - } - } - private fun isFirstInitialization(state: WalletScreenState): Boolean { return state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX } @@ -289,10 +272,6 @@ internal class WalletsUpdateActionResolver @Inject constructor( } } - data object NoAccessibleWallets : Action() - - data object NoWallets : Action() - data object Unknown : Action() } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt index 10d52a0047..d2208db001 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt @@ -1,7 +1,11 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents +import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.card.DeleteSavedAccessCodesUseCase +import com.tangem.core.navigation.AppScreen +import com.tangem.core.navigation.NavigationAction +import com.tangem.core.navigation.ReduxNavController import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.DeleteWalletUseCase @@ -43,6 +47,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase, private val analyticsEventHandler: AnalyticsEventHandler, private val reduxStateHolder: ReduxStateHolder, + private val reduxNavController: ReduxNavController, private val dispatchers: CoroutineDispatcherProvider, ) : BaseWalletClickIntents(), WalletCardClickIntents { @@ -81,18 +86,26 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( viewModelScope.launch(dispatchers.main) { walletScreenContentLoader.cancel(userWalletId) - val deletedUserWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: return@launch + val walletToDelete = getUserWalletUseCase(userWalletId).getOrNull() ?: return@launch + val hasUserWallets = deleteWalletUseCase(userWalletId).getOrElse { + Timber.e("Unable to delete user wallet: $it") + return@launch + } - deleteSavedAccessCodesUseCase(cardId = deletedUserWallet.cardId) - .onLeft { Timber.e(it.toString()) } + deleteSavedAccessCodesUseCase(cardId = walletToDelete.cardId).onLeft { + Timber.e("Unable to delete user wallet access code: $it") + } - deleteWalletUseCase(userWalletId) - .onRight { - getSelectedWalletSyncUseCase().getOrNull()?.let { - reduxStateHolder.onUserWalletSelected(it) - } + if (hasUserWallets) { + val selectedWallet = getSelectedWalletSyncUseCase().getOrElse { + error("Unable to find selected wallet: $it") } - .onLeft { Timber.e(it.toString()) } + + reduxStateHolder.onUserWalletSelected(selectedWallet) + } else { + stateHolder.clear() + reduxNavController.navigate(NavigationAction.PopBackTo(AppScreen.Home)) + } } } } \ No newline at end of file From aa52b659ce3bc86a169857681d6e5e9f43df4451 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 May 2024 21:31:23 +0400 Subject: [PATCH 76/98] Updated on 2026-08-14 --- .../walletconnect/WalletConnectSdkHelper.kt | 33 ++++++++++++++----- .../data/DefaultWalletConnectRepository.kt | 12 ++++--- .../domain/WalletConnectInteractor.kt | 18 +++++++--- .../domain/WalletConnectRepository.kt | 2 +- .../domain/WcSessionRequestConverter.kt | 18 ++++++---- .../domain/models/WalletConnectError.kt | 12 ++++--- .../walletconnect/WalletConnectMiddleware.kt | 13 ++++++++ 7 files changed, 79 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index cd502dc24e..2dfd015a7e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -48,19 +48,30 @@ class WalletConnectSdkHelper { } @Suppress("MagicNumber") - suspend fun prepareTransactionData(data: EthTransactionData): WcTransactionData? { + suspend fun prepareTransactionData(data: EthTransactionData): WcTransactionData { val transaction = data.transaction - val blockchain = Blockchain.fromNetworkId(data.networkId) ?: return null - val walletManager = getWalletManager(blockchain, data.rawDerivationPath) ?: return null + val blockchain = requireNotNull(Blockchain.fromNetworkId(data.networkId)) { + "Blockchain not found" + } + val walletManager = requireNotNull(getWalletManager(blockchain, data.rawDerivationPath)) { + "WalletManager not found" + } walletManager.safeUpdate(isDemoCard()) val wallet = walletManager.wallet - val balance = wallet.amounts[AmountType.Coin]?.value ?: return null + val balance = requireNotNull(wallet.amounts[AmountType.Coin]?.value) { + "Coin balance not found" + } val decimals = wallet.blockchain.decimals() - val value = (transaction.value ?: "0").hexToBigDecimal() - .movePointLeft(decimals) ?: return null + val value = (transaction.value ?: "0") + .hexToBigDecimal() + .movePointLeft(decimals) + + requireNotNull(value) { + "Transaction amount is null" + } val gasLimit = getGasLimitFromTx(value, walletManager, transaction) @@ -69,20 +80,23 @@ class WalletConnectSdkHelper { is Result.Success -> result.data.toBigDecimal() is Result.Failure -> { (result.error as? Throwable)?.let { Timber.e(it, "getGasPrice failed") } - return null + + error("Unable to get gas price: ${result.error}") } - null -> return null + null -> error("Gas price is null") } val fee = (gasLimit * gasPrice).movePointLeft(decimals) val total = value + fee + val destinationAddress = requireNotNull(transaction.to) { "Destination address is null" } + val transactionData = TransactionData( amount = Amount(value, wallet.blockchain), // TODO refactoring fee = Fee.Common(Amount(fee, wallet.blockchain)), sourceAddress = transaction.from, - destinationAddress = transaction.to!!, + destinationAddress = destinationAddress, extras = EthereumTransactionExtras( data = transaction.data.removePrefix(HEX_PREFIX).hexToBytes(), gasLimit = gasLimit.toBigInteger(), @@ -102,6 +116,7 @@ class WalletConnectSdkHelper { id = data.id, type = data.type, ) + return WcTransactionData( type = data.type, transaction = transactionData, diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectRepository.kt index a4c78ad4b9..df6ad4d1af 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectRepository.kt @@ -374,6 +374,7 @@ internal class DefaultWalletConnectRepository( override fun rejectRequest(requestData: RequestData, error: WalletConnectError) { val session = currentSessions.find { it.topic == requestData.topic } + analyticsHandler.send( WalletConnect.RequestHandled( WalletConnect.RequestHandledParams( @@ -385,17 +386,18 @@ internal class DefaultWalletConnectRepository( ), ), ) - cancelRequest(requestData.topic, requestData.requestId) + + cancelRequest(requestData.topic, requestData.requestId, error.error) } - override fun cancelRequest(topic: String, id: Long) { + override fun cancelRequest(topic: String, id: Long, message: String) { Web3Wallet.respondSessionRequest( params = Wallet.Params.SessionRequestResponse( sessionTopic = topic, jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcError( id = id, code = 0, - message = "", + message = message, ), ), onSuccess = {}, @@ -406,8 +408,8 @@ internal class DefaultWalletConnectRepository( override fun reject() { Web3Wallet.rejectSession( params = Wallet.Params.SessionReject( - sessionProposal?.proposerPublicKey ?: "", - "", + proposerPublicKey = sessionProposal?.proposerPublicKey ?: "", + reason = "", ), onSuccess = { Timber.d("Rejected successfully: $it") diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index e2e43d4975..187f705e63 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -260,10 +260,18 @@ class WalletConnectInteractor( ) else -> { currentRequest = sessionRequest - val data = prepareRequestData(sessionRequest) - if (data != null) { - handler.onSessionRequest(data) + + val data = prepareRequestData(sessionRequest).getOrElse { e -> + val wrappedError = e as? WalletConnectError ?: WalletConnectError.UnknownError( + message = e.localizedMessage ?: "Unknown error", + ) + + walletConnectRepository.rejectRequest(requestData, wrappedError) + handler.onSessionRejected(wrappedError) + return } + + handler.onSessionRequest(data) } } } @@ -353,7 +361,9 @@ class WalletConnectInteractor( } } - private suspend fun prepareRequestData(sessionRequest: WalletConnectEvents.SessionRequest): WcPreparedRequest? { + private suspend fun prepareRequestData( + sessionRequest: WalletConnectEvents.SessionRequest, + ): Result { return sessionRequestConverter.prepareRequest(sessionRequest, userWalletId) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectRepository.kt index 33d62fca22..23e7f0bc82 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectRepository.kt @@ -27,5 +27,5 @@ interface WalletConnectRepository { fun rejectRequest(requestData: RequestData, error: WalletConnectError) - fun cancelRequest(topic: String, id: Long) + fun cancelRequest(topic: String, id: Long, message: String = "") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt index 12085cad12..e02d7cc679 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt @@ -4,6 +4,7 @@ import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper import com.tangem.tap.domain.walletconnect2.domain.mapper.mapToTransaction import com.tangem.tap.domain.walletconnect2.domain.models.BnbData import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType @@ -17,15 +18,18 @@ internal class WcSessionRequestConverter( suspend fun prepareRequest( sessionRequest: WalletConnectEvents.SessionRequest, userWalletId: String, - ): WcPreparedRequest? { - val networkId = blockchainHelper.chainIdToNetworkIdOrNull(sessionRequest.chainId ?: "") ?: return null + ): Result = runCatching { + val networkId = requireNotNull(blockchainHelper.chainIdToNetworkIdOrNull(sessionRequest.chainId.orEmpty())) { + "Failed to get network ID for chain ID: ${sessionRequest.chainId}" + } val derivationPath = getDerivationPath( sessionsRepository = sessionsRepository, sessionRequest = sessionRequest, userWalletId = userWalletId, walletAddress = getWalletAddress(sessionRequest.request), ) - return when (val request = sessionRequest.request) { + + when (val request = sessionRequest.request) { is WcRequest.EthSendTransaction -> { val data = sdkHelper.prepareTransactionData( EthTransactionData( @@ -38,7 +42,8 @@ internal class WcSessionRequestConverter( metaName = sessionRequest.metaName, metaUrl = sessionRequest.metaUrl, ), - ) ?: return null + ) + WcPreparedRequest.EthTransaction( preparedRequestData = data, topic = sessionRequest.topic, @@ -58,7 +63,8 @@ internal class WcSessionRequestConverter( metaName = sessionRequest.metaName, metaUrl = sessionRequest.metaUrl, ), - ) ?: return null + ) + WcPreparedRequest.EthTransaction( preparedRequestData = data, topic = sessionRequest.topic, @@ -122,7 +128,7 @@ internal class WcSessionRequestConverter( derivationPath = derivationPath, ) } - else -> null + else -> throw WalletConnectError.UnsupportedMethod } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt index 7d6504d923..40d82cdb51 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt @@ -20,8 +20,12 @@ sealed class WalletConnectError(val error: String) : Exception() { override val message: String?, ) : WalletConnectError("ExternalApprovalError") - object WrongUserWallet : WalletConnectError("WrongUserWallet") - object UnsupportedMethod : WalletConnectError("UnsupportedMethod") - object SigningError : WalletConnectError("SigningError") - object ValidationError : WalletConnectError("ValidationError") + data class UnknownError( + override val message: String, + ) : WalletConnectError(message) + + data object WrongUserWallet : WalletConnectError("WrongUserWallet") + data object UnsupportedMethod : WalletConnectError("UnsupportedMethod") + data object SigningError : WalletConnectError("SigningError") + data object ValidationError : WalletConnectError("ValidationError") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index b480ea809c..7b34183f53 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -9,6 +9,7 @@ import com.tangem.domain.qrscanning.models.SourceType import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.inject +import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor @@ -20,6 +21,7 @@ import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.wallet.R import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware @@ -135,6 +137,17 @@ class WalletConnectMiddleware { ), ) } + is WalletConnectError.UnknownError -> { + store.dispatchOnMain( + GlobalAction.ShowDialog( + AppDialog.SimpleOkDialogRes( + headerId = R.string.wallet_connect_title, + messageId = R.string.wallet_connect_error_with_framework_message, + args = listOf(action.error.message), + ), + ), + ) + } is WalletConnectError.ExternalApprovalError -> { Timber.e(action.error, "ExternalApprovalError ${action.error.message}") // do not show dialog on this event From 4d028cc8e4eece9e4e7c100ce2bbbb6a2bce5a93 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 May 2024 14:24:27 +0500 Subject: [PATCH 77/98] Updated on 2026-08-14 --- .../SdkTransactionHistoryItemConverter.kt | 4 ++- .../utils/SdkTransactionTypeConverter.kt | 27 ++++++++++--------- gradle/dependencies.toml | 2 +- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt index ba58967107..2e1fba8fe9 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt @@ -58,7 +58,9 @@ internal class SdkTransactionHistoryItemConverter( } else { mapToInteractionAddressType(sourceType = sourceType) } - is SdkTransactionHistoryItem.TransactionType.ContractMethod -> mapToInteractionAddressType(destinationType) + is SdkTransactionHistoryItem.TransactionType.ContractMethod, + is SdkTransactionHistoryItem.TransactionType.ContractMethodName, + -> mapToInteractionAddressType(destinationType) } } diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt index 29ea70a4cb..ac4402be26 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt @@ -9,7 +9,7 @@ import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.walletmanager.model.SmartContractMethod import com.tangem.utils.converter.Converter -class SdkTransactionTypeConverter( +internal class SdkTransactionTypeConverter( private val assetReader: AssetReader, private val moshi: Moshi, ) : Converter { @@ -27,20 +27,23 @@ class SdkTransactionTypeConverter( override fun convert(value: TransactionHistoryItem.TransactionType): TxHistoryItem.TransactionType { return when (value) { - TransactionHistoryItem.TransactionType.Transfer -> TxHistoryItem.TransactionType.Transfer - is TransactionHistoryItem.TransactionType.ContractMethod -> { - return when (val name = smartContractMethods[value.id]?.name) { - "transfer" -> TxHistoryItem.TransactionType.Transfer - "approve" -> TxHistoryItem.TransactionType.Approve - "swap" -> TxHistoryItem.TransactionType.Swap - null -> TxHistoryItem.TransactionType.UnknownOperation - else -> TxHistoryItem.TransactionType.Operation(name = name.replaceFirstChar { it.titlecase() }) - } - } + is TransactionHistoryItem.TransactionType.ContractMethod -> + getTransactionType(methodName = smartContractMethods[value.id]?.name) + is TransactionHistoryItem.TransactionType.ContractMethodName -> getTransactionType(methodName = value.name) + is TransactionHistoryItem.TransactionType.Transfer -> TxHistoryItem.TransactionType.Transfer + } + } + + private fun getTransactionType(methodName: String?): TxHistoryItem.TransactionType { + return when (methodName) { + "transfer" -> TxHistoryItem.TransactionType.Transfer + "approve" -> TxHistoryItem.TransactionType.Approve + "swap" -> TxHistoryItem.TransactionType.Swap + null -> TxHistoryItem.TransactionType.UnknownOperation + else -> TxHistoryItem.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() }) } } - @Deprecated(message = "Use AssetReader instead") private fun readSmartContractMethods(): Map { val json = assetReader.readJson("contract_methods") return adapter.fromJson(json) ?: emptyMap() diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 7c24809756..ca1fe3ed21 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -87,7 +87,7 @@ markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-633" +tangemBlockchainSdk = "develop-635" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-354" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From f04fdd1406e313b7f0670cc9c33eb7a8e831a35b Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 May 2024 17:10:19 +0800 Subject: [PATCH 78/98] Updated on 2026-08-14 --- .../wallet/state/model/WalletEvent.kt | 4 +-- .../wallet/ui/WalletEventEffect.kt | 11 ++++--- .../presentation/wallet/ui/WalletScreen.kt | 32 +++++++++++++++++-- .../WalletCurrencyActionsClickIntents.kt | 15 +++++---- .../intents/WalletWarningsClickIntents.kt | 2 +- 5 files changed, 46 insertions(+), 18 deletions(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt index 5ee98fda79..50a1af41c3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt @@ -10,11 +10,9 @@ internal sealed class WalletEvent { data class ShowError(val text: TextReference) : WalletEvent() - data class ShowToast(val text: TextReference) : WalletEvent() - data class ShowAlert(val state: WalletAlertState) : WalletEvent() - data class CopyAddress(val address: String, val toast: TextReference) : WalletEvent() + data class CopyAddress(val address: String) : WalletEvent() data class RateApp(val onDismissClick: () -> Unit) : WalletEvent() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt index cce277150d..19cd88f97f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt @@ -1,7 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.ui -import android.widget.Toast import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope @@ -11,6 +11,7 @@ import androidx.compose.ui.text.AnnotatedString import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.resolveReference +import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.ui.utils.ReviewManagerRequester @@ -42,12 +43,12 @@ internal fun WalletEventEffect( is WalletEvent.ShowError -> { snackbarHostState.showSnackbar(message = value.text.resolveReference(resources)) } - is WalletEvent.ShowToast -> { - Toast.makeText(context, value.text.resolveReference(resources), Toast.LENGTH_SHORT).show() - } is WalletEvent.CopyAddress -> { clipboardManager.setText(AnnotatedString(value.address)) - Toast.makeText(context, value.toast.resolveReference(resources), Toast.LENGTH_SHORT).show() + snackbarHostState.showSnackbar( + message = resources.getString(R.string.wallet_notification_address_copied), + duration = SnackbarDuration.Short, + ) } is WalletEvent.ShowAlert -> onAlertConfigSet(value.state) is WalletEvent.RateApp -> { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 670f85b03a..539e8e9859 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -38,7 +38,10 @@ import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBot import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheet import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig import com.tangem.core.ui.components.keyboardAsState +import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar +import com.tangem.core.ui.components.snackbar.TangemSnackbar import com.tangem.core.ui.components.transactions.state.TxHistoryState +import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.test.TestTags import com.tangem.feature.wallet.impl.R @@ -288,7 +291,11 @@ private fun BaseScaffoldManageTokenRedesign( BottomSheetScaffold( snackbarHost = { - SnackbarHost(hostState = snackbarHostState) + WalletSnackbarHost( + snackbarHostState = it, + event = state.event, + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing16), + ) }, containerColor = TangemTheme.colors.background.secondary, sheetContainerColor = TangemTheme.colors.background.primary, @@ -492,7 +499,13 @@ private fun BaseScaffold( ) { Scaffold( topBar = { WalletTopBar(config = state.topBarConfig) }, - snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, + snackbarHost = { + WalletSnackbarHost( + snackbarHostState = snackbarHostState, + event = state.event, + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing16), + ) + }, floatingActionButton = { val manageTokensButtonConfig by remember(state.selectedWalletIndex) { mutableStateOf( @@ -529,6 +542,21 @@ private fun BaseScaffold( ) } +@Composable +private fun WalletSnackbarHost( + snackbarHostState: SnackbarHostState, + event: StateEvent, + modifier: Modifier = Modifier, +) { + SnackbarHost(hostState = snackbarHostState, modifier = modifier) { data -> + if (event is StateEvent.Triggered && event.data is WalletEvent.CopyAddress) { + CopiedTextSnackbar(data) + } else { + TangemSnackbar(data) + } + } +} + @Composable private fun ManageTokensButton(onClick: () -> Unit) { PrimaryButton( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index d45b21bf9c..e472dd6eac 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -7,7 +7,10 @@ import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBot import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels -import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.WrappedList +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.common.util.cardTypesResolver @@ -39,7 +42,6 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch -import java.lang.IllegalArgumentException import javax.inject.Inject interface WalletCurrencyActionsClickIntents { @@ -204,11 +206,10 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( .find { it.type == AddressType.Default } ?.value ?.let { + stateHolder.update(CloseBottomSheetTransformer(userWalletId = stateHolder.getSelectedWalletId())) + walletEventSender.send( - event = WalletEvent.CopyAddress( - address = it, - toast = resourceReference(R.string.wallet_notification_address_copied), - ), + event = WalletEvent.CopyAddress(address = it), ) } } @@ -270,7 +271,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( .fold( ifLeft = { walletEventSender.send( - event = WalletEvent.ShowToast(text = resourceReference(R.string.common_error)), + event = WalletEvent.ShowError(text = resourceReference(R.string.common_error)), ) }, ifRight = { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index 418a4d8afd..21982d8010 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -154,7 +154,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( val event = when (error) { is UnlockWalletsError.DataError, is UnlockWalletsError.UnableToUnlockWallets, - -> WalletEvent.ShowToast(resourceReference(R.string.user_wallet_list_error_unable_to_unlock)) + -> WalletEvent.ShowError(resourceReference(R.string.user_wallet_list_error_unable_to_unlock)) is UnlockWalletsError.NoUserWalletSelected, is UnlockWalletsError.NotAllUserWalletsUnlocked, -> WalletEvent.ShowAlert(WalletAlertState.RescanWallets) From 70c989be4e628ebc29725d9fb7a1ce6e3aa8ece2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 May 2024 17:10:50 +0800 Subject: [PATCH 79/98] Updated on 2026-08-14 --- .../tokenreceive/TokenReceiveBottomSheet.kt | 255 +++++++++++------- 1 file changed, 159 insertions(+), 96 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt index 1057a43847..46317b571e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt @@ -1,6 +1,5 @@ package com.tangem.core.ui.components.bottomsheets.tokenreceive -import android.widget.Toast import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.* import androidx.compose.foundation.layout.* @@ -9,10 +8,12 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalClipboardManager @@ -26,9 +27,11 @@ import com.tangem.core.ui.components.SecondaryButtonIconStart import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.rememberQrPainters +import com.tangem.core.ui.components.snackbar.CopiedTextSnackbarHost import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.shareText import com.tangem.core.ui.res.TangemTheme +import kotlinx.coroutines.launch @Composable fun TokenReceiveBottomSheet(config: TangemBottomSheetConfig) { @@ -40,117 +43,88 @@ fun TokenReceiveBottomSheet(config: TangemBottomSheetConfig) { @Composable private fun TokenReceiveBottomSheetContent(content: TokenReceiveBottomSheetConfig) { var selectedAddress by remember { mutableStateOf(content.addresses.first()) } - Column( - modifier = Modifier - .verticalScroll(state = rememberScrollState()) - .padding( - start = TangemTheme.dimens.spacing24, - top = TangemTheme.dimens.spacing24, - end = TangemTheme.dimens.spacing24, - bottom = TangemTheme.dimens.spacing16, - ), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing24), - ) { - QrCodeContent( - content = content, - onAddressChange = { selectedAddress = it }, - ) - Text( - text = stringResource(R.string.receive_bottom_sheet_warning_message_full, content.name), - color = TangemTheme.colors.text.secondary, - textAlign = TextAlign.Center, - style = TangemTheme.typography.caption2, - ) - Row( - horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), + + val snackbarHostState = remember(::SnackbarHostState) + + ContainerWithSnackbarHost(snackbarHostState = snackbarHostState) { + Column( + modifier = Modifier + .verticalScroll(state = rememberScrollState()) + .padding( + start = TangemTheme.dimens.spacing24, + top = TangemTheme.dimens.spacing24, + end = TangemTheme.dimens.spacing24, + bottom = TangemTheme.dimens.spacing16, + ), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing24), ) { - val clipboardManager = LocalClipboardManager.current - val hapticFeedback = LocalHapticFeedback.current - val context = LocalContext.current - SecondaryButtonIconStart( - modifier = Modifier.weight(1f), - text = stringResource(id = R.string.common_copy), - iconResId = R.drawable.ic_copy_24, - onClick = { - content.onCopyClick.invoke() - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - clipboardManager.setText(AnnotatedString(selectedAddress.value)) - Toast.makeText(context, R.string.wallet_notification_address_copied, Toast.LENGTH_SHORT).show() - }, - ) - SecondaryButtonIconStart( - modifier = Modifier.weight(1f), - text = stringResource(id = R.string.common_share), - iconResId = R.drawable.ic_share_24, - onClick = { - content.onShareClick.invoke() - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - context.shareText(selectedAddress.value) - }, - ) + QrCodeContent(content = content, onAddressChange = { selectedAddress = it }) + + DisclaimerText(text = content.name) + + Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16)) { + CopyButton( + address = selectedAddress.value, + snackbarHostState = snackbarHostState, + onClick = content.onCopyClick, + modifier = Modifier.weight(1f), + ) + + ShareButton( + address = selectedAddress.value, + onClick = content.onShareClick, + modifier = Modifier.weight(1f), + ) + } } } } -@Suppress("LongMethod") +@Composable +private fun ContainerWithSnackbarHost(snackbarHostState: SnackbarHostState, content: @Composable () -> Unit) { + Box { + content() + + CopiedTextSnackbarHost( + hostState = snackbarHostState, + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = TangemTheme.dimens.spacing80), + ) + } +} + @OptIn(ExperimentalFoundationApi::class) @Composable private fun QrCodeContent(content: TokenReceiveBottomSheetConfig, onAddressChange: (AddressModel) -> Unit) { val qrCodes = rememberQrPainters(content.addresses.map(AddressModel::value)) + val pagerState = rememberPagerState( initialPage = 0, initialPageOffsetFraction = 0f, - ) { - content.addresses.count() - } + pageCount = content.addresses::count, + ) LaunchedEffect(key1 = pagerState.currentPage) { onAddressChange.invoke(content.addresses[pagerState.currentPage]) } - HorizontalPager( - state = pagerState, - ) { currentPage -> - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24), - ) { - Text( - text = stringResource( - R.string.receive_bottom_sheet_warning_message, - getName(content = content, index = pagerState.currentPage), - content.symbol, - content.network, - ), - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Center, - style = TangemTheme.typography.h3, - ) - Image( - painter = qrCodes[currentPage], - contentDescription = null, - contentScale = ContentScale.Fit, - modifier = Modifier - .size(TangemTheme.dimens.size248), - ) - Text( - text = content.addresses[currentPage].value, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Center, - style = TangemTheme.typography.subtitle1, - ) - } + HorizontalPager(state = pagerState) { currentPage -> + QrCodePage( + content = content, + qrCodePainter = qrCodes[currentPage], + currentIndex = currentPage, + ) } if (pagerState.pageCount > 1) { val indicatorState = rememberLazyListState() val selectedColor = TangemTheme.colors.icon.primary1 val unselectedColor = TangemTheme.colors.icon.informative + LazyRow( - modifier = Modifier - .height(TangemTheme.dimens.size20), + modifier = Modifier.height(TangemTheme.dimens.size20), state = indicatorState, horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, @@ -158,17 +132,14 @@ private fun QrCodeContent(content: TokenReceiveBottomSheetConfig, onAddressChang repeat(pagerState.pageCount) { iteration -> item(key = iteration) { val color by animateColorAsState( - if (pagerState.currentPage == iteration) selectedColor else unselectedColor, + targetValue = if (pagerState.currentPage == iteration) selectedColor else unselectedColor, + label = "", ) + Box( modifier = Modifier - .padding( - start = TangemTheme.dimens.spacing4, - top = TangemTheme.dimens.spacing6, - end = TangemTheme.dimens.spacing4, - bottom = TangemTheme.dimens.spacing6, - ) - .background(color, CircleShape) + .padding(horizontal = TangemTheme.dimens.spacing4, vertical = TangemTheme.dimens.spacing6) + .background(color = color, shape = CircleShape) .size(TangemTheme.dimens.size7), ) } @@ -177,6 +148,41 @@ private fun QrCodeContent(content: TokenReceiveBottomSheetConfig, onAddressChang } } +@Composable +private fun QrCodePage(content: TokenReceiveBottomSheetConfig, qrCodePainter: Painter, currentIndex: Int) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24), + ) { + Text( + text = stringResource( + R.string.receive_bottom_sheet_warning_message, + getName(content = content, index = currentIndex), + content.symbol, + content.network, + ), + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + style = TangemTheme.typography.h3, + ) + + Image( + painter = qrCodePainter, + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.size(TangemTheme.dimens.size248), + ) + + Text( + text = content.addresses[currentIndex].value, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + style = TangemTheme.typography.subtitle1, + ) + } +} + @Composable private fun getName(content: TokenReceiveBottomSheetConfig, index: Int): String { return if (content.addresses.size < 2) { @@ -184,4 +190,61 @@ private fun getName(content: TokenReceiveBottomSheetConfig, index: Int): String } else { "${content.addresses[index].displayName.resolveReference()} ${content.name}" } +} + +@Composable +private fun DisclaimerText(text: String) { + Text( + text = stringResource(R.string.receive_bottom_sheet_warning_message_full, text), + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + style = TangemTheme.typography.caption2, + ) +} + +@Composable +private fun CopyButton( + address: String, + snackbarHostState: SnackbarHostState, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val hapticFeedback = LocalHapticFeedback.current + val clipboardManager = LocalClipboardManager.current + val coroutineScope = rememberCoroutineScope() + val resources = LocalContext.current.resources + + SecondaryButtonIconStart( + modifier = modifier, + text = stringResource(id = R.string.common_copy), + iconResId = R.drawable.ic_copy_24, + onClick = { + onClick() + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + clipboardManager.setText(AnnotatedString(address)) + + coroutineScope.launch { + snackbarHostState.showSnackbar( + message = resources.getString(R.string.wallet_notification_address_copied), + ) + } + }, + ) +} + +@Composable +private fun ShareButton(address: String, onClick: () -> Unit, modifier: Modifier = Modifier) { + val hapticFeedback = LocalHapticFeedback.current + val context = LocalContext.current + + SecondaryButtonIconStart( + modifier = modifier, + text = stringResource(id = R.string.common_share), + iconResId = R.drawable.ic_share_24, + onClick = { + onClick() + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + context.shareText(address) + }, + ) } \ No newline at end of file From 9da26433fb749a19d1bd8a3ad19aed80b84de6e1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 May 2024 17:11:13 +0800 Subject: [PATCH 80/98] Updated on 2026-08-14 --- .../com/tangem/core/ui/res/TangemDimens.kt | 1 + .../referral/ui/ParticipateBottomBlock.kt | 39 ++-- .../feature/referral/ui/ReferralScreen.kt | 174 ++++++------------ 3 files changed, 74 insertions(+), 140 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index 79a2e16273..447750132a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -131,6 +131,7 @@ data class TangemDimens internal constructor( val spacing92: Dp = 92.dp, val spacing94: Dp = 94.dp, val spacing96: Dp = 96.dp, + val spacing108: Dp = 108.dp, val spacing154: Dp = 154.dp, // endregion Spacing ) \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index 69c37e25c9..7890ba08a7 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -8,14 +8,8 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Divider -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow @@ -31,12 +25,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.core.content.ContextCompat.startActivity -import com.tangem.core.ui.components.* -import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.components.PrimaryStartIconButton import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.referral.domain.models.ExpectedAward import com.tangem.feature.referral.domain.models.ExpectedAwards import com.tangem.feature.referral.presentation.R +import kotlinx.coroutines.launch @Suppress("LongParameterList") @Composable @@ -45,8 +40,8 @@ internal fun ParticipateBottomBlock( code: String, shareLink: String, expectedAwards: ExpectedAwards?, + snackbarHostState: SnackbarHostState, onAgreementClick: () -> Unit, - onShowCopySnackbar: () -> Unit, onCopyClick: () -> Unit, onShareClick: () -> Unit, ) { @@ -63,7 +58,7 @@ internal fun ParticipateBottomBlock( AdditionalButtons( code = code, shareLink = shareLink, - onShowCopySnackbar = onShowCopySnackbar, + snackbarHostState = snackbarHostState, onCopyClick = onCopyClick, onShareClick = onShareClick, ) @@ -112,9 +107,9 @@ private fun Awards(expectedAwards: ExpectedAwards) { val elementsCountToShowInLessMode = 3 val isExpanded = remember { mutableStateOf(false) } - Divider( - color = TangemTheme.colors.stroke.primary, + HorizontalDivider( thickness = TangemTheme.dimens.size0_5, + color = TangemTheme.colors.stroke.primary, ) AwardText( startText = if (expectedAwards.expectedAwards.isNotEmpty()) { @@ -272,13 +267,16 @@ private fun PersonalCodeCard(code: String) { private fun AdditionalButtons( code: String, shareLink: String, - onShowCopySnackbar: () -> Unit, + snackbarHostState: SnackbarHostState, onCopyClick: () -> Unit, onShareClick: () -> Unit, ) { val clipboardManager = LocalClipboardManager.current val hapticFeedback = LocalHapticFeedback.current + val coroutineScope = rememberCoroutineScope() + val resources = LocalContext.current.resources + Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), @@ -291,7 +289,13 @@ private fun AdditionalButtons( onCopyClick.invoke() hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) clipboardManager.setText(AnnotatedString(code)) - onShowCopySnackbar() + + coroutineScope.launch { + snackbarHostState.showSnackbar( + message = resources.getString(R.string.referral_promo_code_copied), + duration = SnackbarDuration.Short, + ) + } }, ) @@ -333,7 +337,7 @@ private fun ParticipateBottomBlockPreview( shareLink = data.shareLink, expectedAwards = data.expectedAwards, onAgreementClick = data.onAgreementClick, - onShowCopySnackbar = data.onShowCopySnackbar, + snackbarHostState = SnackbarHostState(), onCopyClick = data.onCopyClick, onShareClick = data.onShareClick, ) @@ -391,7 +395,6 @@ private class ParticipateBottomBlockDataProvider : CollectionPreviewParameterPro purchasedWalletCount = 0, expectedAwards = null, ), - ), ) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index cdff31bf58..39b216fb16 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -1,6 +1,7 @@ package com.tangem.feature.referral.ui import android.content.res.Configuration +import android.content.res.Resources import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -11,9 +12,7 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -22,14 +21,15 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.components.appbar.AppBarWithBackButton -import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar +import com.tangem.core.ui.components.snackbar.TangemSnackbar import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.referral.domain.models.ExpectedAward import com.tangem.feature.referral.domain.models.ExpectedAwards import com.tangem.feature.referral.models.DemoModeException @@ -50,6 +50,8 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier var isBottomSheetVisible by remember { mutableStateOf(value = false) } val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val snackbarHostState = remember(::SnackbarHostState) + Scaffold( modifier = modifier, topBar = { @@ -58,10 +60,26 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier onBackClick = stateHolder.headerState.onBackClicked, ) }, + snackbarHost = { + SnackbarHost( + hostState = snackbarHostState, + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .padding(bottom = TangemTheme.dimens.spacing108), + ) { + // TODO: use StateEvent + if (stateHolder.errorSnackbar != null) { + TangemSnackbar(data = it, actionOnNewLine = true) + } else { + CopiedTextSnackbar(it) + } + } + }, containerColor = TangemTheme.colors.background.secondary, ) { ReferralContent( stateHolder = stateHolder, + snackbarHostState = snackbarHostState, onAgreementClick = { stateHolder.analytics.onAgreementClicked.invoke() isBottomSheetVisible = true @@ -70,6 +88,26 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier ) } + val errorSnackbar = stateHolder.errorSnackbar + val coroutineScope = rememberCoroutineScope() + val resources = LocalContext.current.resources + + SideEffect { + if (errorSnackbar != null) { + coroutineScope.launch { + val result = snackbarHostState.showSnackbar( + message = resources.getMessageForErrorSnackbar(errorSnackbar.throwable), + actionLabel = resources.getString(R.string.warning_button_ok), + duration = SnackbarDuration.Indefinite, + ) + + if (result == SnackbarResult.ActionPerformed) { + errorSnackbar.onOkClicked() + } + } + } + } + ReferralBottomSheet( sheetState = sheetState, isVisible = isBottomSheetVisible, @@ -81,11 +119,10 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier @Composable private fun ReferralContent( stateHolder: ReferralStateHolder, + snackbarHostState: SnackbarHostState, onAgreementClick: () -> Unit, modifier: Modifier = Modifier, ) { - val isCopyButtonPressed = remember { mutableStateOf(value = false) } - Box(modifier = modifier) { LazyColumn( modifier = Modifier.fillMaxSize(), @@ -95,14 +132,11 @@ private fun ReferralContent( item { ReferralInfo( stateHolder = stateHolder, + snackbarHostState = snackbarHostState, onAgreementClick = onAgreementClick, - onShowCopySnackbar = { isCopyButtonPressed.value = true }, ) } } - - ErrorSnackbarHost(errorSnackbar = stateHolder.errorSnackbar) - CopySnackbarHost(isCopyButtonPressed = isCopyButtonPressed) } } @@ -133,8 +167,8 @@ private fun Header() { @Composable private fun ReferralInfo( stateHolder: ReferralStateHolder, + snackbarHostState: SnackbarHostState, onAgreementClick: () -> Unit, - onShowCopySnackbar: () -> Unit, ) { when (val state = stateHolder.referralInfoState) { is ReferralInfoState.ParticipantContent -> { @@ -144,8 +178,8 @@ private fun ReferralInfo( code = state.code, shareLink = state.shareLink, expectedAwards = state.expectedAwards, + snackbarHostState = snackbarHostState, onAgreementClick = onAgreementClick, - onShowCopySnackbar = onShowCopySnackbar, onCopyClick = stateHolder.analytics.onCopyClicked, onShareClick = stateHolder.analytics.onShareClicked, ) @@ -335,115 +369,11 @@ private fun ShimmerInfo() { } } -// TODO() Replace component with component from ds -@Composable -private fun BoxScope.ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) { - if (errorSnackbar != null) { - val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) } - val coroutineScope = rememberCoroutineScope() - - SnackbarHost( - hostState = snackbarHostState, - modifier = Modifier.align(Alignment.BottomCenter), - snackbar = { - Snackbar( - snackbarData = it, - modifier = Modifier.fillMaxWidth(), - actionOnNewLine = true, - shape = RoundedCornerShape(size = TangemTheme.dimens.radius8), - containerColor = TangemTheme.colors.button.primary, - contentColor = TangemTheme.colors.text.primary2, - actionColor = TangemTheme.colors.text.primary2, - ) - }, - ) - - val actionLabel = stringResource(id = R.string.warning_button_ok) - val message = getMessageForErrorSnackbar(errorSnackbar) - SideEffect { - coroutineScope.launch { - val result = snackbarHostState.showSnackbar( - message = message, - actionLabel = actionLabel, - duration = SnackbarDuration.Indefinite, - ) - if (result == SnackbarResult.ActionPerformed) { - errorSnackbar.onOkClicked() - } - } - } - } -} - -// TODO() Replace component with component from ds -@Composable -private fun BoxScope.CopySnackbarHost(isCopyButtonPressed: MutableState) { - if (isCopyButtonPressed.value) { - val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) } - val coroutineScope = rememberCoroutineScope() - - var snackbarSize by remember { mutableIntStateOf(value = 0) } - val width = LocalConfiguration.current.screenWidthDp.dp - val snackbarWidth = with(LocalDensity.current) { snackbarSize.toDp() } - - SnackbarHost( - hostState = snackbarHostState, - modifier = Modifier - .align(Alignment.BottomCenter) - .padding(start = (width - snackbarWidth).div(2), bottom = TangemTheme.dimens.spacing12) - .fillMaxWidth(), - snackbar = { - Box( - modifier = Modifier - .onSizeChanged { snackbarSize = it.width } - .background( - color = TangemTheme.colors.icon.primary1, - shape = RoundedCornerShape(size = TangemTheme.dimens.radius8), - ) - .padding( - horizontal = TangemTheme.dimens.spacing16, - vertical = TangemTheme.dimens.spacing14, - ), - ) { - Text( - text = it.visuals.message, - color = TangemTheme.colors.text.primary2, - style = TangemTheme.typography.body2, - ) - } - }, - ) - - val message = stringResource(id = R.string.referral_promo_code_copied) - SideEffect { - coroutineScope.launch { - snackbarHostState.showSnackbar( - message = message, - duration = SnackbarDuration.Short, - ) - isCopyButtonPressed.value = false - } - } - } -} - -@Composable -private fun getMessageForErrorSnackbar(errorSnackbar: ErrorSnackbar): String { - return when (errorSnackbar.throwable) { - is DemoModeException -> { - stringResource(id = R.string.alert_demo_feature_disabled) - } - - else -> { - if (errorSnackbar.throwable.cause != null) { - String.format( - format = stringResource(id = R.string.referral_error_failed_to_load_info_with_reason), - errorSnackbar.throwable.cause, - ) - } else { - stringResource(id = R.string.referral_error_failed_to_load_info) - } - } +private fun Resources.getMessageForErrorSnackbar(throwable: Throwable): String { + return when { + throwable is DemoModeException -> getString(R.string.alert_demo_feature_disabled) + throwable.cause != null -> getString(R.string.referral_error_failed_to_load_info_with_reason, throwable.cause) + else -> getString(R.string.referral_error_failed_to_load_info) } } From 2a00ffa8cfc2eefc7359fae0f225fc86ce79b633 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 May 2024 11:54:36 +0100 Subject: [PATCH 81/98] Updated on 2026-08-14 --- gradle/dependencies.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index ca1fe3ed21..ec97c63128 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -87,9 +87,9 @@ markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-635" +tangemBlockchainSdk = "develop-637" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-354" +tangemCardSdk = "develop-356" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem From 68e5d9d174f17b8b3d1005e09478fcecc9d66243 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 May 2024 12:29:24 +0100 Subject: [PATCH 82/98] Updated on 2026-08-14 --- .../repository/DelegatedKeystoreManager.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt index 7e7a781189..3db56b3dcb 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DelegatedKeystoreManager.kt @@ -8,15 +8,20 @@ internal class DelegatedKeystoreManager( private val keystoreManagerProvider: Provider, ) : KeystoreManager { - override suspend fun get(masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAlias: String): SecretKey? { - return keystoreManagerProvider().get(masterKeyConfig, keyAlias) + override suspend fun get( + masterKeyConfig: KeystoreManager.MasterKeyConfig, + keyAlias: String, + forceAuthentication: Boolean, + ): SecretKey? { + return keystoreManagerProvider().get(masterKeyConfig, keyAlias, forceAuthentication) } override suspend fun get( masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAliases: Set, + forceAuthentication: Boolean, ): Map { - return keystoreManagerProvider().get(masterKeyConfig, keyAliases) + return keystoreManagerProvider().get(masterKeyConfig, keyAliases, forceAuthentication) } override suspend fun store(masterKeyConfig: KeystoreManager.MasterKeyConfig, keyAlias: String, key: SecretKey) { From 947ad15975ef48dfcd23365ca4bf109b2201c1c0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 May 2024 13:57:30 +0200 Subject: [PATCH 83/98] Updated on 2026-08-14 --- .../java/com/tangem/tap/di/ActivityModule.kt | 8 ++++++ .../features/tester/api/AppRestarter.kt | 9 +++++++ .../feature/tester/ActivityClassWrapper.kt | 12 +++++++++ .../apprestarter/DefaultAppRestarter.kt | 27 +++++++++++++++++++ .../feature/tester/di/RestarterModule.kt | 26 ++++++++++++++++++ .../tester/presentation/TesterActivity.kt | 6 ++++- .../actions/TesterActionsContentState.kt | 5 ++-- .../actions/TesterActionsScreen.kt | 3 ++- .../actions/TesterActionsViewModel.kt | 3 ++- .../state/FeatureTogglesContentState.kt | 4 ++- .../featuretoggles/ui/FeatureTogglesScreen.kt | 10 +++++++ .../viewmodels/FeatureTogglesViewModel.kt | 13 ++++++--- .../impl/src/main/res/values/strings.xml | 1 + 13 files changed, 117 insertions(+), 10 deletions(-) create mode 100644 features/tester/api/src/main/java/com/tangem/features/tester/api/AppRestarter.kt create mode 100644 features/tester/impl/src/main/java/com/tangem/feature/tester/ActivityClassWrapper.kt create mode 100644 features/tester/impl/src/main/java/com/tangem/feature/tester/apprestarter/DefaultAppRestarter.kt create mode 100644 features/tester/impl/src/main/java/com/tangem/feature/tester/di/RestarterModule.kt diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index 7a386bdfd1..cbf8eebb27 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -6,6 +6,8 @@ import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import com.tangem.feature.tester.ActivityClassWrapper +import com.tangem.tap.MainActivity import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository import com.tangem.tap.network.exchangeServices.DefaultRampManager @@ -65,4 +67,10 @@ internal object ActivityModule { ): GetPolkadotCheckHasImmortalUseCase { return GetPolkadotCheckHasImmortalUseCase(polkadotAccountHealthCheckRepository) } + + @Provides + @Singleton + fun provideActivityClassWrapper(): ActivityClassWrapper { + return ActivityClassWrapper(MainActivity::class.java) + } } \ No newline at end of file diff --git a/features/tester/api/src/main/java/com/tangem/features/tester/api/AppRestarter.kt b/features/tester/api/src/main/java/com/tangem/features/tester/api/AppRestarter.kt new file mode 100644 index 0000000000..a64be25542 --- /dev/null +++ b/features/tester/api/src/main/java/com/tangem/features/tester/api/AppRestarter.kt @@ -0,0 +1,9 @@ +package com.tangem.features.tester.api + +/** + * Interface for app restarter + */ +interface AppRestarter { + + fun restart() +} \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/ActivityClassWrapper.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/ActivityClassWrapper.kt new file mode 100644 index 0000000000..22d2d4d3a9 --- /dev/null +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/ActivityClassWrapper.kt @@ -0,0 +1,12 @@ +package com.tangem.feature.tester + +import android.app.Activity + +/** + * Wraps the main activity class to avoid type erasure issues during injection. + * + * @property clazz activity class + */ +class ActivityClassWrapper( + val clazz: Class, +) \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/apprestarter/DefaultAppRestarter.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/apprestarter/DefaultAppRestarter.kt new file mode 100644 index 0000000000..34cd927e71 --- /dev/null +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/apprestarter/DefaultAppRestarter.kt @@ -0,0 +1,27 @@ +package com.tangem.feature.tester.apprestarter + +import android.app.Activity +import android.content.Context +import android.content.Intent +import com.tangem.feature.tester.ActivityClassWrapper +import com.tangem.features.tester.api.AppRestarter + +/** + * Entity that kills the process and restarts the main activity + * @property context Activity context + */ +internal class DefaultAppRestarter( + private val context: Context, + private val activityClassWrapper: ActivityClassWrapper, +) : AppRestarter { + + override fun restart() { + if (context !is Activity) return + + context.finish() + context.startActivity( + Intent(context, activityClassWrapper.clazz).apply { flags = Intent.FLAG_ACTIVITY_CLEAR_TOP }, + ) + Runtime.getRuntime().exit(0) + } +} \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/di/RestarterModule.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/di/RestarterModule.kt new file mode 100644 index 0000000000..4891a60f12 --- /dev/null +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/di/RestarterModule.kt @@ -0,0 +1,26 @@ +package com.tangem.feature.tester.di + +import android.content.Context +import com.tangem.feature.tester.ActivityClassWrapper +import com.tangem.feature.tester.apprestarter.DefaultAppRestarter +import com.tangem.features.tester.api.AppRestarter +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 RestarterModule { + + @Provides + @ActivityScoped + fun provideAppRestarter( + @ActivityContext context: Context, + activityClassWrapper: ActivityClassWrapper, + ): AppRestarter { + return DefaultAppRestarter(context, activityClassWrapper) + } +} \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt index 2ddfba7af0..ef7ab21998 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt @@ -18,6 +18,7 @@ import com.tangem.feature.tester.presentation.menu.state.TesterMenuContentState import com.tangem.feature.tester.presentation.menu.ui.TesterMenuScreen import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter import com.tangem.feature.tester.presentation.navigation.TesterScreen +import com.tangem.features.tester.api.AppRestarter import com.tangem.features.tester.api.TesterRouter import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -33,6 +34,9 @@ internal class TesterActivity : ComposeActivity() { @Inject lateinit var testerRouter: TesterRouter + @Inject + lateinit var appRestarter: AppRestarter + private val innerTesterRouter: InnerTesterRouter get() = requireNotNull(testerRouter as? InnerTesterRouter) { "TesterRouter must be InnerTesterRouter for tester feature" @@ -66,7 +70,7 @@ internal class TesterActivity : ComposeActivity() { composable(route = TesterScreen.FEATURE_TOGGLES.name) { val viewModel = hiltViewModel().apply { - setupNavigation(innerTesterRouter) + setupInteractions(innerTesterRouter, appRestarter) } FeatureTogglesScreen(state = viewModel.uiState) diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsContentState.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsContentState.kt index fa65d935e4..c814bf750b 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsContentState.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsContentState.kt @@ -3,15 +3,16 @@ package com.tangem.feature.tester.presentation.actions import com.tangem.domain.apptheme.model.AppThemeMode internal data class TesterActionsContentState( - val onBackClick: () -> Unit, val hideAllCurrenciesConfig: HideAllCurrenciesConfig, val toggleAppThemeConfig: ToggleAppThemeConfig, + val onBackClick: () -> Unit, + val onApplyChangesClick: () -> Unit, ) internal sealed class HideAllCurrenciesConfig { data class Clickable(val onClick: () -> Unit) : HideAllCurrenciesConfig() - object Progress : HideAllCurrenciesConfig() + data object Progress : HideAllCurrenciesConfig() } internal data class ToggleAppThemeConfig( diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt index 830d10591c..1cc0845d8c 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt @@ -78,9 +78,10 @@ private fun TesterActionsScreenSample(modifier: Modifier = Modifier) { ) { TesterActionsScreen( state = TesterActionsContentState( - onBackClick = {}, hideAllCurrenciesConfig = HideAllCurrenciesConfig.Clickable {}, toggleAppThemeConfig = ToggleAppThemeConfig(AppThemeMode.DEFAULT) {}, + onBackClick = {}, + onApplyChangesClick = {}, ), ) } diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt index b919f37132..514edb448d 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt @@ -31,9 +31,10 @@ internal class TesterActionsViewModel @Inject constructor( private val initialState: TesterActionsContentState get() = TesterActionsContentState( - onBackClick = { /* no-op */ }, hideAllCurrenciesConfig = HideAllCurrenciesConfig.Clickable(this::hideAllCurrencies), toggleAppThemeConfig = ToggleAppThemeConfig(AppThemeMode.DEFAULT, this::toggleAppTheme), + onBackClick = { /* no-op */ }, + onApplyChangesClick = { /* no-op */ }, ) init { diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/state/FeatureTogglesContentState.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/state/FeatureTogglesContentState.kt index 4c55258e0d..736e01b6c6 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/state/FeatureTogglesContentState.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/state/FeatureTogglesContentState.kt @@ -8,9 +8,11 @@ import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatur * @property featureToggles feature toggles list * @property onBackClick the lambda to be invoked when back button is pressed * @property onToggleValueChange the lambda to be invoked when switch button is pressed + * @property onApplyChangesClick the lambda to be invoked when apply changes button is pressed */ internal data class FeatureTogglesContentState( val featureToggles: List, - val onBackClick: () -> Unit, val onToggleValueChange: (String, Boolean) -> Unit, + val onBackClick: () -> Unit, + val onApplyChangesClick: () -> Unit, ) \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt index 74dddd26f0..b43d13d2f3 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -51,6 +52,14 @@ internal fun FeatureTogglesScreen(state: FeatureTogglesContentState) { onCheckedChange = { isChange -> state.onToggleValueChange(featureToggle.name, isChange) }, ) } + item { + PrimaryButton( + text = stringResource(id = R.string.apply_changes), + onClick = state.onApplyChangesClick, + modifier = Modifier.fillMaxWidth() + .padding(TangemTheme.dimens.spacing16), + ) + } } } @@ -95,6 +104,7 @@ private fun PreviewFeatureTogglesScreen() { ), onToggleValueChange = { _, _ -> }, onBackClick = {}, + onApplyChangesClick = {}, ), ) } diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt index 037946a145..d3459ac31f 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt @@ -10,6 +10,7 @@ import com.tangem.core.featuretoggle.manager.MutableFeatureTogglesManager import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatureToggle import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureTogglesContentState import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter +import com.tangem.features.tester.api.AppRestarter import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch @@ -38,16 +39,20 @@ internal class FeatureTogglesViewModel @Inject constructor( "Feature toggle manager must be mutable (debug build type)" } - /** Setup navigation state property by router [router] */ - fun setupNavigation(router: InnerTesterRouter) { - uiState = uiState.copy(onBackClick = router::back) + /** Setup navigation state property by router [router] and provides app restart method by [appRestarter] */ + fun setupInteractions(router: InnerTesterRouter, appRestarter: AppRestarter) { + uiState = uiState.copy( + onBackClick = router::back, + onApplyChangesClick = appRestarter::restart, + ) } private fun initState(): FeatureTogglesContentState { return FeatureTogglesContentState( featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles(), - onBackClick = {}, onToggleValueChange = ::onToggleValueChange, + onBackClick = {}, + onApplyChangesClick = {}, ) } diff --git a/features/tester/impl/src/main/res/values/strings.xml b/features/tester/impl/src/main/res/values/strings.xml index 9a5a5846b6..ffc3114361 100644 --- a/features/tester/impl/src/main/res/values/strings.xml +++ b/features/tester/impl/src/main/res/values/strings.xml @@ -2,6 +2,7 @@ Tester menu Feature toggles + Apply changes Stand toggles Tester actions Hide all currencies From 78996521c68c1c336b64d2971a68d83ad7146362 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 May 2024 18:38:47 +0100 Subject: [PATCH 84/98] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../tokendetails/state/components/TokenDetailsDialogConfig.kt | 4 ++-- .../tokendetails/state/factory/TokenDetailsStateFactory.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index a494a9b493..7ae8d89566 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit a494a9b49335d891dc5836d478c843432a857621 +Subproject commit 7ae8d8956605c444a46a5b15cfa0a11cb595ca9f diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt index 82bb4bbd12..f0bccdec33 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt @@ -31,13 +31,13 @@ internal data class TokenDetailsDialogConfig( ) data class ConfirmHideConfig( - val currencySymbol: String, + val currencyTitle: String, val onConfirmClick: () -> Unit, val onCancelClick: () -> Unit, ) : DialogContentConfig() { override val title: TextReference = TextReference.Res( id = R.string.token_details_hide_alert_title, - formatArgs = wrappedList(currencySymbol), + formatArgs = wrappedList(currencyTitle), ) override val message: TextReference = TextReference.Res(R.string.token_details_hide_alert_message) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 82d586ce66..db67d88385 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -152,7 +152,7 @@ internal class TokenDetailsStateFactory( isShow = true, onDismissRequest = clickIntents::onDismissDialog, content = TokenDetailsDialogConfig.DialogContentConfig.ConfirmHideConfig( - currencySymbol = currency.symbol, + currencyTitle = currency.name, onConfirmClick = clickIntents::onHideConfirmed, onCancelClick = clickIntents::onDismissDialog, ), From ab5fac9196d7825e896b4befbf719f4f9e3d6db2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 May 2024 12:02:55 +0200 Subject: [PATCH 85/98] Updated on 2026-08-14 --- .../com/tangem/tap/ApplicationEntryPoint.kt | 3 + .../java/com/tangem/tap/TangemApplication.kt | 5 ++ .../CardContextInterceptor.kt | 2 +- .../common/feedback/AdditionalFeedbackInfo.kt | 2 +- .../tap/di/domain/CardLegacyDomainModule.kt | 10 +++- .../tap/di/domain/WalletsDomainModule.kt | 26 ++++++++ .../card/DefaultDerivationsRepository.kt | 2 +- .../com/tangem/tap/domain/extensions/Card.kt | 6 +- .../domain/tasks/product/DerivationsFinder.kt | 2 +- .../BiometricUserWalletsListManager.kt | 3 + .../GeneralUserWalletsListManager.kt | 3 + .../RuntimeUserWalletsListManager.kt | 3 + .../details/redux/DetailsMiddleware.kt | 7 ++- .../tap/features/home/redux/HomeMiddleware.kt | 5 +- .../features/onboarding/OnboardingHelper.kt | 7 ++- .../twins/redux/TwinCardsMiddleware.kt | 2 +- .../twins/ui/OnboardingTwinsFragment.kt | 6 +- .../redux/OnboardingWalletMiddleware.kt | 7 ++- .../saveWallet/redux/SaveWalletMiddleware.kt | 5 +- .../welcome/redux/WelcomeMiddleware.kt | 6 +- .../tap/proxy/redux/DaggerGraphState.kt | 2 + .../local/preferences/PreferencesKeys.kt | 2 + .../DefaultWalletNamesMigrationRepository.kt | 23 +++++++ .../data/wallets/di/WalletsDataModule.kt | 8 +++ domain/legacy/build.gradle.kts | 9 ++- .../wallets/builder}/UserWalletBuilder.kt | 30 ++++------ .../wallets/builder}/UserWalletIdBuilder.kt | 2 +- .../wallets/legacy/UserWalletsListManager.kt | 3 + .../tangem/domain/wallets/models}/Artwork.kt | 6 +- .../wallets/models/UpdateWalletError.kt | 4 +- .../WalletNamesMigrationRepository.kt | 11 ++++ .../usecase/GenerateWalletNameUseCase.kt | 60 +++++++++++++++++++ .../wallets/usecase}/GetCardImageUseCase.kt | 7 ++- .../wallets/usecase/GetWalletNamesUseCase.kt | 13 ++++ .../wallets/usecase/RenameWalletUseCase.kt | 36 +++++++++++ .../ScanCardToUnlockWalletClickHandler.kt | 6 +- .../domain/WalletNameMigrationUseCase.kt | 49 +++++++++++++++ .../wallet/state/model/WalletAlertState.kt | 2 + .../presentation/wallet/ui/WalletAlert.kt | 6 +- .../wallet/viewmodels/WalletViewModel.kt | 9 +++ .../intents/WalletCardClickIntents.kt | 39 ++++++++---- 41 files changed, 366 insertions(+), 73 deletions(-) create mode 100644 data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletNamesMigrationRepository.kt rename domain/{legacy/src/main/java/com/tangem/domain/userwallets => wallets/src/main/java/com/tangem/domain/wallets/builder}/UserWalletBuilder.kt (69%) rename domain/{legacy/src/main/java/com/tangem/domain/userwallets => wallets/src/main/java/com/tangem/domain/wallets/builder}/UserWalletIdBuilder.kt (98%) rename domain/{legacy/src/main/java/com/tangem/domain/userwallets => wallets/src/main/java/com/tangem/domain/wallets/models}/Artwork.kt (66%) create mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletNamesMigrationRepository.kt create mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GenerateWalletNameUseCase.kt rename domain/{legacy/src/main/java/com/tangem/domain/userwallets => wallets/src/main/java/com/tangem/domain/wallets/usecase}/GetCardImageUseCase.kt (89%) create mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletNamesUseCase.kt create mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletNameMigrationUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index b7b4d6a9a0..5099008e5d 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -20,6 +20,7 @@ import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository @@ -86,6 +87,8 @@ interface ApplicationEntryPoint { fun getSaveTwinsOnboardingShownUseCase(): SaveTwinsOnboardingShownUseCase + fun getWalletNameGenerateUseCase(): GenerateWalletNameUseCase + fun getCardRepository(): CardRepository fun getFeedbackManagerFeatureToggles(): FeedbackManagerFeatureToggles diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 5a22b825cb..80c118ae61 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -37,6 +37,7 @@ import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository @@ -152,6 +153,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase get() = entryPoint.getSaveTwinsOnboardingShownUseCase() + private val generateWalletNameUseCase: GenerateWalletNameUseCase + get() = entryPoint.getWalletNameGenerateUseCase() + private val cardRepository: CardRepository get() = entryPoint.getCardRepository() @@ -243,6 +247,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { generalUserWalletsListManager = generalUserWalletsListManager, wasTwinsOnboardingShownUseCase = wasTwinsOnboardingShownUseCase, saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase, + generateWalletNameUseCase = generateWalletNameUseCase, cardRepository = cardRepository, feedbackManagerFeatureToggles = feedbackManagerFeatureToggles, tangemSdkLogger = tangemSdkLogger, diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt index 9ba9bbbf87..e7c6feee04 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt @@ -5,7 +5,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.userwallets.UserWalletIdBuilder +import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.IntroductionProcess diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt index 975fb1fb2f..e87ca04616 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt @@ -6,7 +6,7 @@ import com.tangem.blockchain.common.address.Address import com.tangem.crypto.NetworkType import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.userwallets.UserWalletIdBuilder +import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.tap.common.extensions.stripZeroPlainString import java.util.concurrent.CopyOnWriteArrayList diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardLegacyDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardLegacyDomainModule.kt index 3cfbe41b23..9654238c85 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardLegacyDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardLegacyDomainModule.kt @@ -1,6 +1,8 @@ package com.tangem.tap.di.domain import com.tangem.domain.card.* +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.tap.domain.scanCard.DefaultScanCardProcessor import dagger.Module import dagger.Provides @@ -14,5 +16,11 @@ internal object CardLegacyDomainModule { @Provides @Singleton - fun provideScanCardUseCase(): ScanCardProcessor = DefaultScanCardProcessor() + fun provideScanCardProcessor(): ScanCardProcessor = DefaultScanCardProcessor() + + @Provides + @Singleton + fun providesWalletNameGenerateUseCase(userWalletsListManager: UserWalletsListManager): GenerateWalletNameUseCase { + return GenerateWalletNameUseCase(userWalletsListManager) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index de5aa6d294..444ce645e5 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -4,8 +4,10 @@ import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletAddressServiceRepository +import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.* +import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -23,6 +25,18 @@ internal object WalletsDomainModule { return GetWalletsUseCase(userWalletsListManager = userWalletsListManager) } + @Provides + @ViewModelScoped + fun providesWalletNameMigrationUseCase( + userWalletsListManager: UserWalletsListManager, + walletNamesMigrationRepository: WalletNamesMigrationRepository, + ): WalletNameMigrationUseCase { + return WalletNameMigrationUseCase( + userWalletsListManager = userWalletsListManager, + walletNamesMigrationRepository = walletNamesMigrationRepository, + ) + } + @Provides @ViewModelScoped fun providesGetUserWalletUseCase(userWalletsListManager: UserWalletsListManager): GetUserWalletUseCase { @@ -76,6 +90,18 @@ internal object WalletsDomainModule { return UpdateWalletUseCase(userWalletsListManager = userWalletsListManager) } + @Provides + @ViewModelScoped + fun providesRenameWalletUseCase(userWalletsListManager: UserWalletsListManager): RenameWalletUseCase { + return RenameWalletUseCase(userWalletsListManager = userWalletsListManager) + } + + @Provides + @ViewModelScoped + fun providesGetWalletsSyncUseCase(userWalletsListManager: UserWalletsListManager): GetWalletNamesUseCase { + return GetWalletNamesUseCase(userWalletsListManager = userWalletsListManager) + } + @Provides @ViewModelScoped fun providesDeleteWalletUseCase(userWalletsListManager: UserWalletsListManager): DeleteWalletUseCase { diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt index d5fe11422f..b7ad02d630 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt @@ -9,7 +9,7 @@ import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.userwallets.UserWalletIdBuilder +import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.ExtendedPublicKeysMap diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index c71548c0bd..9497f54791 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -5,7 +5,7 @@ import com.tangem.common.services.Result import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.getTwinCardNumber import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.userwallets.Artwork +import com.tangem.domain.wallets.models.Artwork import com.tangem.operations.attestation.CardVerifyAndGetInfo import com.tangem.operations.attestation.OnlineCardVerifier @@ -20,8 +20,8 @@ suspend fun CardDTO.getOrLoadCardArtworkUrl(cardInfo: Result Artwork.MARTA_CARD_URL else -> { when (getTwinCardNumber()) { - TwinCardNumber.First -> Artwork.TWIN_CARD_1 - TwinCardNumber.Second -> Artwork.TWIN_CARD_2 + TwinCardNumber.First -> Artwork.TWIN_CARD_1_URL + TwinCardNumber.Second -> Artwork.TWIN_CARD_2_URL else -> Artwork.DEFAULT_IMG_URL } } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt index cb4b7918e9..d1f7b8a3d6 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt @@ -9,7 +9,7 @@ import com.tangem.datasource.local.token.UserTokensStore import com.tangem.domain.common.DerivationStyleProvider import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.userwallets.UserWalletIdBuilder +import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.features.demo.DemoHelper import com.tangem.utils.coroutines.CoroutineDispatcherProvider diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 96419972cb..e359900a32 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -43,6 +43,9 @@ internal class BiometricUserWalletsListManager( .filterNotNull() .distinctUntilChanged() + override val userWalletsSync: List + get() = state.value.userWallets + override val selectedUserWalletSync: UserWallet? get() = findSelectedUserWallet() diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt index abec2ac6e8..09818c2351 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -61,6 +61,9 @@ internal class GeneralUserWalletsListManager( } } + override val userWalletsSync: List + get() = requireImplementation.userWalletsSync + override val selectedUserWalletSync: UserWallet? get() = requireImplementation.selectedUserWalletSync diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt index ca9a5bd68e..4d211d9b88 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt @@ -26,6 +26,9 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager { .filterNotNull() .distinctUntilChanged() + override val userWalletsSync: List + get() = listOfNotNull(state.value.userWallet) + override val selectedUserWalletSync: UserWallet? get() = state.value.userWallet diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index edef362685..495afd0915 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -15,8 +15,8 @@ import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.userwallets.UserWalletBuilder -import com.tangem.domain.userwallets.UserWalletIdBuilder +import com.tangem.domain.wallets.builder.UserWalletBuilder +import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.legacy.asLockable import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -522,7 +522,8 @@ class DetailsMiddleware { } private suspend fun saveUserWalletAndPopBackToWalletScreen(scanResponse: ScanResponse): CompletionResult { - val userWallet = UserWalletBuilder(scanResponse).build() + val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase) + val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase).build() ?: return CompletionResult.Failure(TangemSdkError.WalletIsNotCreated()) val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 862e4ccb8a..17e8d7dd0a 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -11,7 +11,7 @@ import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.userwallets.UserWalletBuilder +import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.analytics.events.Shop @@ -103,7 +103,8 @@ private suspend fun readCard() { } private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch { - val userWallet = UserWalletBuilder(scanResponse).build().guard { + val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase) + val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase).build().guard { Timber.e("User wallet not created") return@launch } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 0c6c5601a2..f03dc73425 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -12,8 +12,8 @@ import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.userwallets.UserWalletBuilder -import com.tangem.domain.userwallets.UserWalletIdBuilder +import com.tangem.domain.wallets.builder.UserWalletBuilder +import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.extensions.* import com.tangem.tap.features.demo.DemoHelper @@ -142,7 +142,8 @@ object OnboardingHelper { backupCardsIds: List?, hasBackupError: Boolean, ) { - val userWallet = UserWalletBuilder(scanResponse = scanResponse) + val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase) + val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase) .hasBackupError(hasBackupError) .backupCardsIds(backupCardsIds?.toSet()) .build() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index a1c84cb01a..b59d2efa0b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -12,7 +12,7 @@ import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.userwallets.UserWalletIdBuilder +import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.legacy.asLockable import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt index ca8d61618e..b4260dd2e8 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt @@ -18,7 +18,7 @@ import com.tangem.core.ui.extensions.setStatusBarColor import com.tangem.datasource.asset.reader.AssetReader import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.userwallets.Artwork +import com.tangem.domain.wallets.models.Artwork import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.* @@ -88,13 +88,13 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment( binding.toolbar.title = getText(R.string.twins_recreate_toolbar) - mainBinding.onboardingTopContainer.imvTwinFrontCard.load(Artwork.TWIN_CARD_1) { + mainBinding.onboardingTopContainer.imvTwinFrontCard.load(Artwork.TWIN_CARD_1_URL) { placeholder(R.drawable.card_placeholder_black) error(R.drawable.card_placeholder_black) fallback(R.drawable.card_placeholder_black) } - mainBinding.onboardingTopContainer.imvTwinBackCard.load(Artwork.TWIN_CARD_2) { + mainBinding.onboardingTopContainer.imvTwinBackCard.load(Artwork.TWIN_CARD_2_URL) { placeholder(R.drawable.card_placeholder_white) error(R.drawable.card_placeholder_white) fallback(R.drawable.card_placeholder_white) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 3a6b93da1a..925dc9bcc2 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -15,8 +15,8 @@ import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.userwallets.Artwork -import com.tangem.domain.userwallets.UserWalletBuilder +import com.tangem.domain.wallets.models.Artwork +import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.feature.onboarding.data.model.CreateWalletResponse import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator @@ -548,7 +548,8 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } if (scanResponse != null) { - val userWallet = UserWalletBuilder(scanResponse) + val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase) + val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase) .backupCardsIds(backupState.backupCardIds.toSet()) .build() .guard { diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index bdfffe8f9f..45eb5225a2 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -6,7 +6,7 @@ import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction -import com.tangem.domain.userwallets.UserWalletBuilder +import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.domain.wallets.models.UserWallet import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen @@ -64,7 +64,8 @@ internal class SaveWalletMiddleware { scope.launch { val backupInfo = state.backupInfo ?: error("Backup info is null") - val userWallet = UserWalletBuilder(backupInfo.scanResponse) + val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase) + val userWallet = UserWalletBuilder(backupInfo.scanResponse, walletNameGenerateUseCase) .backupCardsIds(state.backupInfo.backupCardsIds) .hasBackupError(hasBackupError) .build() diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 4956441790..1f1c3132a0 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -13,7 +13,7 @@ import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.userwallets.UserWalletBuilder +import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType import com.tangem.domain.wallets.legacy.unlockIfLockable import com.tangem.tap.backupService @@ -117,7 +117,9 @@ internal class WelcomeMiddleware { ) scanCardInternal { scanResponse -> - val userWallet = UserWalletBuilder(scanResponse).build() ?: return@scanCardInternal + val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase) + val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase).build() + ?: return@scanCardInternal val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) userWalletsListManager.save(userWallet, canOverride = true) diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 9001b33eea..0c09261f91 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -16,6 +16,7 @@ import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository @@ -62,6 +63,7 @@ data class DaggerGraphState( val generalUserWalletsListManager: UserWalletsListManager? = null, val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase? = null, val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase? = null, + val generateWalletNameUseCase: GenerateWalletNameUseCase? = null, val cardRepository: CardRepository? = null, val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles? = null, val tangemSdkLogger: TangemSdkLogger? = null, diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index cfafab3c05..e069422752 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -91,6 +91,8 @@ object PreferencesKeys { val SHOULD_SAVE_ACCESS_CODES_KEY by lazy { booleanPreferencesKey(name = "saveAccessCodes") } + val IS_WALLET_NAMES_MIGRATION_DONE_KEY by lazy { booleanPreferencesKey(name = "isWalletNamesMigrationDone") } + fun getStart2CoinTOSAcceptedKey(region: String?) = booleanPreferencesKey(name = "start2Coin_tos_accepted_$region") } diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletNamesMigrationRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletNamesMigrationRepository.kt new file mode 100644 index 0000000000..60d442028e --- /dev/null +++ b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletNamesMigrationRepository.kt @@ -0,0 +1,23 @@ +package com.tangem.data.wallets + +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getSyncOrDefault +import com.tangem.datasource.local.preferences.utils.store +import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository + +class DefaultWalletNamesMigrationRepository( + private val appPreferencesStore: AppPreferencesStore, +) : WalletNamesMigrationRepository { + + override suspend fun isMigrationDone(): Boolean { + return appPreferencesStore.getSyncOrDefault( + key = PreferencesKeys.IS_WALLET_NAMES_MIGRATION_DONE_KEY, + default = false, + ) + } + + override suspend fun setMigrationDone() { + appPreferencesStore.store(PreferencesKeys.IS_WALLET_NAMES_MIGRATION_DONE_KEY, true) + } +} \ No newline at end of file diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt b/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt index 26e9f82859..9748691618 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt @@ -1,9 +1,11 @@ package com.tangem.data.wallets.di +import com.tangem.data.wallets.DefaultWalletNamesMigrationRepository import com.tangem.data.wallets.DefaultWalletAddressServiceRepository import com.tangem.data.wallets.DefaultWalletsRepository import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository import com.tangem.domain.wallets.repository.WalletAddressServiceRepository import com.tangem.domain.wallets.repository.WalletsRepository import dagger.Module @@ -29,4 +31,10 @@ internal object WalletsDataModule { ): WalletAddressServiceRepository { return DefaultWalletAddressServiceRepository(walletManagersFacade) } + + @Provides + @Singleton + fun provideMigrateNamesRepository(appPreferencesStore: AppPreferencesStore): WalletNamesMigrationRepository { + return DefaultWalletNamesMigrationRepository(appPreferencesStore) + } } \ No newline at end of file diff --git a/domain/legacy/build.gradle.kts b/domain/legacy/build.gradle.kts index f21bb97aa5..f13641bdc1 100644 --- a/domain/legacy/build.gradle.kts +++ b/domain/legacy/build.gradle.kts @@ -9,17 +9,16 @@ android { } dependencies { - implementation(project(":core:datasource")) - implementation(project(":core:utils")) - implementation(project(":common")) - implementation(project(":libs:auth")) + implementation(projects.core.datasource) + implementation(projects.core.utils) + implementation(projects.common) + implementation(projects.libs.auth) implementation(projects.libs.blockchainSdk) implementation(projects.domain.demo) implementation(projects.domain.models) implementation(projects.domain.tokens.models) implementation(projects.domain.txhistory.models) implementation(projects.domain.wallets.models) - /** Tangem libraries */ implementation(deps.tangem.blockchain) { exclude(module = "joda-time") diff --git a/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletBuilder.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/builder/UserWalletBuilder.kt similarity index 69% rename from domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletBuilder.kt rename to domain/wallets/src/main/java/com/tangem/domain/wallets/builder/UserWalletBuilder.kt index 325fb2efa7..ffa69e47ae 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletBuilder.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/builder/UserWalletBuilder.kt @@ -1,36 +1,22 @@ -package com.tangem.domain.userwallets +package com.tangem.domain.wallets.builder import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.wallets.usecase.GetCardImageUseCase import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase class UserWalletBuilder( private val scanResponse: ScanResponse, + private val generateWalletNameUseCase: GenerateWalletNameUseCase, private val getCardImageUseCase: GetCardImageUseCase = GetCardImageUseCase(), ) { private var backupCardsIds: Set = emptySet() private var hasBackupError: Boolean = false private val CardDTO.isBackupNotAllowed: Boolean - get() = !this.settings.isBackupAllowed - - private val ScanResponse.userWalletName: String - get() = when (productType) { - ProductType.Note -> "Note" - ProductType.Twins -> "Twin" - ProductType.Start2Coin -> "Start2Coin" - ProductType.Visa -> "Tangem Visa" - ProductType.Wallet, - ProductType.Wallet2, - ProductType.Ring, - -> when { - card.isBackupNotAllowed -> "Tangem card" - cardTypesResolver.isStart2Coin() -> "Start2Coin" - else -> "Wallet" - } - } + get() = !settings.isBackupAllowed /** * DANGEROUS!!! @@ -56,7 +42,11 @@ class UserWalletBuilder( ?.let { UserWallet( walletId = it, - name = userWalletName, + name = generateWalletNameUseCase( + productType = productType, + isBackupNotAllowed = card.isBackupNotAllowed, + isStartToCoin = cardTypesResolver.isStart2Coin(), + ), artworkUrl = getCardImageUseCase.invoke(card.cardId, card.cardPublicKey), cardsInWallet = backupCardsIds.plus(card.cardId), scanResponse = this, diff --git a/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletIdBuilder.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/builder/UserWalletIdBuilder.kt similarity index 98% rename from domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletIdBuilder.kt rename to domain/wallets/src/main/java/com/tangem/domain/wallets/builder/UserWalletIdBuilder.kt index 607484d8a8..7c916ecec1 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletIdBuilder.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/builder/UserWalletIdBuilder.kt @@ -1,4 +1,4 @@ -package com.tangem.domain.userwallets +package com.tangem.domain.wallets.builder import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.hexToBytes diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt index e51ef51dc7..d98eb009aa 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt @@ -18,6 +18,9 @@ interface UserWalletsListManager { /** [Flow] with selected [UserWallet] updates */ val selectedUserWallet: Flow + /** All saved [UserWallet]s */ + val userWalletsSync: List + /** Selected [UserWallet] */ val selectedUserWalletSync: UserWallet? diff --git a/domain/legacy/src/main/java/com/tangem/domain/userwallets/Artwork.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/Artwork.kt similarity index 66% rename from domain/legacy/src/main/java/com/tangem/domain/userwallets/Artwork.kt rename to domain/wallets/src/main/java/com/tangem/domain/wallets/models/Artwork.kt index d788aed532..00b7961d48 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/userwallets/Artwork.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/Artwork.kt @@ -1,4 +1,4 @@ -package com.tangem.domain.userwallets +package com.tangem.domain.wallets.models data class Artwork(val artworkId: String) { @@ -6,9 +6,9 @@ data class Artwork(val artworkId: String) { const val DEFAULT_IMG_URL = "https://app.tangem.com/cards/card_default.png" const val SERGIO_CARD_URL = "https://app.tangem.com/cards/card_tg059.png" const val MARTA_CARD_URL = "https://app.tangem.com/cards/card_tg083.png" + const val TWIN_CARD_1_URL = "https://app.tangem.com/cards/card_tg085.png" + const val TWIN_CARD_2_URL = "https://app.tangem.com/cards/card_tg086.png" const val SERGIO_CARD_ID = "BC01" const val MARTA_CARD_ID = "BC02" - const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png" - const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png" } } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/UpdateWalletError.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/UpdateWalletError.kt index 48a93dd33b..b15ec332d1 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/UpdateWalletError.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/UpdateWalletError.kt @@ -2,5 +2,7 @@ package com.tangem.domain.wallets.models sealed interface UpdateWalletError { - object DataError : UpdateWalletError + data object DataError : UpdateWalletError + + data object NameAlreadyExists : UpdateWalletError } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletNamesMigrationRepository.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletNamesMigrationRepository.kt new file mode 100644 index 0000000000..0fa502f042 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletNamesMigrationRepository.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.wallets.repository + +/** + * Access to migrate names flag + */ +interface WalletNamesMigrationRepository { + + suspend fun isMigrationDone(): Boolean + + suspend fun setMigrationDone() +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GenerateWalletNameUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GenerateWalletNameUseCase.kt new file mode 100644 index 0000000000..f1d1852f0c --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GenerateWalletNameUseCase.kt @@ -0,0 +1,60 @@ +package com.tangem.domain.wallets.usecase + +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.wallets.legacy.UserWalletsListManager + +/** + * Use case for user wallet name generation + */ +class GenerateWalletNameUseCase( + private val userWalletsListManager: UserWalletsListManager, +) { + + operator fun invoke(productType: ProductType, isBackupNotAllowed: Boolean, isStartToCoin: Boolean): String { + val defaultName = getDefaultName( + productType = productType, + isBackupNotAllowed = isBackupNotAllowed, + isStartToCoin = isStartToCoin, + ) + + val existingNames = userWalletsListManager.userWalletsSync.map { it.name }.toSet() + return suggestedWalletName(defaultName, existingNames) + } + + private fun suggestedWalletName(defaultName: String, existingNames: Set): String { + val startIndex = 2 + if (!existingNames.contains(defaultName)) { + return defaultName + } + + for (index in startIndex..MAX_WALLETS_LIMIT) { + val potentialName = "$defaultName $index" + if (!existingNames.contains(potentialName)) { + return potentialName + } + } + + return defaultName + } + + private fun getDefaultName(productType: ProductType, isBackupNotAllowed: Boolean, isStartToCoin: Boolean): String { + return when (productType) { + ProductType.Note -> "Note" + ProductType.Twins -> "Twin" + ProductType.Start2Coin -> "Start2Coin" + ProductType.Visa -> "Tangem Visa" + ProductType.Wallet, + ProductType.Wallet2, + ProductType.Ring, + -> when { + isBackupNotAllowed -> "Tangem card" + isStartToCoin -> "Start2Coin" + else -> "Wallet" + } + } + } + + companion object { + const val MAX_WALLETS_LIMIT = 10000 + } +} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/userwallets/GetCardImageUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt similarity index 89% rename from domain/legacy/src/main/java/com/tangem/domain/userwallets/GetCardImageUseCase.kt rename to domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt index afa5eaaa37..3d92875a5b 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/userwallets/GetCardImageUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt @@ -1,9 +1,10 @@ -package com.tangem.domain.userwallets +package com.tangem.domain.wallets.usecase import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinsHelper +import com.tangem.domain.wallets.models.Artwork import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.operations.attestation.TangemApi @@ -42,8 +43,8 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardV cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL else -> when (TwinsHelper.getTwinCardNumber(cardId)) { - TwinCardNumber.First -> Artwork.TWIN_CARD_1 - TwinCardNumber.Second -> Artwork.TWIN_CARD_2 + TwinCardNumber.First -> Artwork.TWIN_CARD_1_URL + TwinCardNumber.Second -> Artwork.TWIN_CARD_2_URL else -> Artwork.DEFAULT_IMG_URL } } diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletNamesUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletNamesUseCase.kt new file mode 100644 index 0000000000..0108e03b67 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletNamesUseCase.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.wallets.usecase + +import com.tangem.domain.wallets.legacy.UserWalletsListManager + +/** + * Use case for getting list of user wallets names. + * + * @property userWalletsListManager user wallets list manager + */ +class GetWalletNamesUseCase(private val userWalletsListManager: UserWalletsListManager) { + + operator fun invoke(): List = userWalletsListManager.userWalletsSync.map { it.name } +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt new file mode 100644 index 0000000000..e03da26f6b --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/RenameWalletUseCase.kt @@ -0,0 +1,36 @@ +package com.tangem.domain.wallets.usecase + +import arrow.core.Either +import arrow.core.left +import arrow.core.raise.either +import arrow.core.right +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.models.UpdateWalletError +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Use case for rename user wallet + * + * @property userWalletsListManager user wallets list manager + */ +class RenameWalletUseCase(private val userWalletsListManager: UserWalletsListManager) { + + suspend operator fun invoke(userWalletId: UserWalletId, name: String): Either { + val existingNames = userWalletsListManager.userWalletsSync + + if (existingNames.any { it.name == name && it.walletId != userWalletId }) { + return UpdateWalletError.NameAlreadyExists.left() + } + + return either { + userWalletsListManager.update(userWalletId) { it.copy(name = name) } + .doOnSuccess { return it.right() } + .doOnFailure { return UpdateWalletError.DataError.left() } + + return UpdateWalletError.DataError.left() + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/ScanCardToUnlockWalletClickHandler.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/ScanCardToUnlockWalletClickHandler.kt index 1a4a34780d..e528262365 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/ScanCardToUnlockWalletClickHandler.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/ScanCardToUnlockWalletClickHandler.kt @@ -6,7 +6,8 @@ import arrow.core.raise.ensure import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.domain.card.ScanCardProcessor -import com.tangem.domain.userwallets.UserWalletBuilder +import com.tangem.domain.wallets.builder.UserWalletBuilder +import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.SaveWalletUseCase import javax.inject.Inject @@ -14,6 +15,7 @@ import javax.inject.Inject internal class ScanCardToUnlockWalletClickHandler @Inject constructor( private val scanCardProcessor: ScanCardProcessor, private val saveWalletUseCase: SaveWalletUseCase, + private val generateWalletNameUseCase: GenerateWalletNameUseCase, ) { private var scanFailsCounter = 0 @@ -31,7 +33,7 @@ internal class ScanCardToUnlockWalletClickHandler @Inject constructor( scanFailsCounter = 0 // If card's public key is null then user wallet will be null - val scannedWallet = UserWalletBuilder(scanResponse = result.data).build() + val scannedWallet = UserWalletBuilder(result.data, generateWalletNameUseCase).build() ensure(walletId == scannedWallet?.walletId) { ScanCardToUnlockWalletError.WrongCardIsScanned diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletNameMigrationUseCase.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletNameMigrationUseCase.kt new file mode 100644 index 0000000000..99e60b3682 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletNameMigrationUseCase.kt @@ -0,0 +1,49 @@ +package com.tangem.feature.wallet.presentation.wallet.domain + +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository +import timber.log.Timber + +class WalletNameMigrationUseCase( + private val userWalletsListManager: UserWalletsListManager, + private val walletNamesMigrationRepository: WalletNamesMigrationRepository, +) { + + suspend operator fun invoke() { + val wallets = userWalletsListManager.userWalletsSync + + if (walletNamesMigrationRepository.isMigrationDone()) { + return + } + + val existingNames: MutableSet = mutableSetOf() + wallets.indices.forEach { i -> + val defaultName = wallets[i].name + val suggestedWalletName = suggestedWalletName(defaultName, existingNames) + if (defaultName != suggestedWalletName) { + userWalletsListManager.update(wallets[i].walletId) { it.copy(name = suggestedWalletName) } + } + Timber.tag("Migrated names").e(i.toString() + " " + suggestedWalletName) + } + + walletNamesMigrationRepository.setMigrationDone() + } + + private fun suggestedWalletName(defaultName: String, existingNames: MutableSet): String { + val startIndex = 1 + for (index in startIndex..MAX_WALLETS_LIMIT) { + val name = if (index == startIndex) defaultName else "$defaultName $index" + + if (!existingNames.contains(name)) { + existingNames.add(name) + return name + } + } + + return defaultName + } + + companion object { + const val MAX_WALLETS_LIMIT = 10000 + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt index 34afb01364..03fb7f7902 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt @@ -25,6 +25,7 @@ internal sealed interface WalletAlertState { open val text: String = "" open val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok) abstract val onConfirmClick: (String) -> Unit + abstract val errorTextProvider: (String) -> TextReference? } data class DefaultAlert( @@ -36,6 +37,7 @@ internal sealed interface WalletAlertState { data class RenameWalletAlert( override val text: String, override val onConfirmClick: (String) -> Unit, + override val errorTextProvider: (String) -> TextReference?, ) : TextInput() { override val title: TextReference = resourceReference(id = R.string.user_wallet_list_rename_popup_title) override val label: TextReference = resourceReference(id = R.string.user_wallet_list_rename_popup_placeholder) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletAlert.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletAlert.kt index c5303e085e..48e959ad54 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletAlert.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletAlert.kt @@ -64,7 +64,9 @@ private fun TextInputAlert(state: WalletAlertState.TextInput, onDismiss: () -> U fieldValue = value, confirmButton = DialogButton( title = state.confirmButtonText.resolveReference(), - enabled = value.text.isNotEmpty() && value.text != state.text, + enabled = value.text.isNotEmpty() && + value.text != state.text && + state.errorTextProvider(value.text) == null, onClick = { state.onConfirmClick(value.text) onDismiss() @@ -76,6 +78,8 @@ private fun TextInputAlert(state: WalletAlertState.TextInput, onDismiss: () -> U dismissButton = DialogButton(title = stringResource(id = R.string.common_cancel), onClick = onDismiss), textFieldParams = AdditionalTextInputDialogParams( label = state.label.resolveReference(), + isError = state.errorTextProvider(value.text) != null, + caption = state.errorTextProvider(value.text)?.resolveReference(), ), ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index d7865401e0..912b4c81a3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -15,6 +15,7 @@ import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWalletAnalyticsSender +import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent @@ -57,6 +58,7 @@ internal class WalletViewModel @Inject constructor( private val screenLifecycleProvider: ScreenLifecycleProvider, private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender, private val walletDeepLinksHandler: WalletDeepLinksHandler, + private val walletNameMigrationUseCase: WalletNameMigrationUseCase, ) : ViewModel() { val uiState: StateFlow = stateHolder.uiState @@ -71,12 +73,19 @@ internal class WalletViewModel @Inject constructor( suggestToEnableBiometrics() + maybeMigrateNames() subscribeToUserWalletsUpdates() subscribeOnBalanceHiding() subscribeOnSelectedWalletFlow() subscribeToScreenBackgroundState() } + private fun maybeMigrateNames() { + viewModelScope.launch { + walletNameMigrationUseCase() + } + } + fun setWalletRouter(router: InnerWalletRouter) { this.router = router clickIntents.initialize(router, viewModelScope) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt index d2208db001..5b4bf78f5d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt @@ -2,6 +2,11 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.domain.wallets.usecase.GetWalletNamesUseCase +import com.tangem.domain.wallets.usecase.RenameWalletUseCase +import com.tangem.feature.wallet.impl.R import com.tangem.domain.card.DeleteSavedAccessCodesUseCase import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction @@ -11,7 +16,6 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.DeleteWalletUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase -import com.tangem.domain.wallets.usecase.UpdateWalletUseCase import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController @@ -40,9 +44,10 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, private val walletEventSender: WalletEventSender, private val walletScreenContentLoader: WalletScreenContentLoader, + private val renameWalletUseCase: RenameWalletUseCase, + private val getWalletNamesUseCase: GetWalletNamesUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, - private val updateWalletUseCase: UpdateWalletUseCase, private val deleteWalletUseCase: DeleteWalletUseCase, private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase, private val analyticsEventHandler: AnalyticsEventHandler, @@ -54,19 +59,33 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( override fun onRenameBeforeConfirmationClick(userWalletId: UserWalletId) { analyticsEventHandler.send(MainScreen.EditWalletTapped) - walletEventSender.send( - event = WalletEvent.ShowAlert( - state = WalletAlertState.RenameWalletAlert( - text = stateHolder.getSelectedWallet().walletCardState.title, - onConfirmClick = { onRenameAfterConfirmationClick(userWalletId, it) }, + viewModelScope.launch(dispatchers.main) { + val walletNames = getWalletNamesUseCase() + val currentWalletName = stateHolder.getSelectedWallet().walletCardState.title + walletEventSender.send( + event = WalletEvent.ShowAlert( + state = WalletAlertState.RenameWalletAlert( + text = currentWalletName, + onConfirmClick = { onRenameAfterConfirmationClick(userWalletId, it) }, + errorTextProvider = { enteredName -> + if (walletNames.contains(enteredName) && enteredName != currentWalletName) { + resourceReference( + R.string.user_wallet_list_rename_popup_error_already_exists, + wrappedList(enteredName), + ) + } else { + null + } + }, + ), ), - ), - ) + ) + } } override fun onRenameAfterConfirmationClick(userWalletId: UserWalletId, name: String) { viewModelScope.launch(dispatchers.main) { - updateWalletUseCase(userWalletId = userWalletId, update = { it.copy(name = name) }) + renameWalletUseCase(userWalletId = userWalletId, name) } } From a298e77060b9c48c24851da5ead65628a7752f39 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 May 2024 21:21:01 +0100 Subject: [PATCH 86/98] Updated on 2026-08-14 --- .../BiometricUserWalletsListManager.kt | 6 ++-- .../GeneralUserWalletsListManager.kt | 6 ++-- .../repository/DefaultTxHistoryRepository.kt | 2 +- .../wallets/legacy/UserWalletsListManager.kt | 2 +- .../wallets/usecase/GetUserWalletUseCase.kt | 5 ++- .../viewmodels/TokenDetailsViewModel.kt | 34 ++++++++----------- 6 files changed, 25 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index e359900a32..52e9be2c6d 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -35,6 +35,9 @@ internal class BiometricUserWalletsListManager( .mapLatest { it.userWallets } .distinctUntilChanged() + override val userWalletsSync: List + get() = state.value.userWallets + override val selectedUserWallet: Flow get() = state .mapLatest { state -> @@ -43,9 +46,6 @@ internal class BiometricUserWalletsListManager( .filterNotNull() .distinctUntilChanged() - override val userWalletsSync: List - get() = state.value.userWallets - override val selectedUserWalletSync: UserWallet? get() = findSelectedUserWallet() diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt index 09818c2351..4565c111ed 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -54,6 +54,9 @@ internal class GeneralUserWalletsListManager( } } + override val userWalletsSync: List + get() = requireImplementation.userWalletsSync + override val selectedUserWallet: Flow get() = implementation.transformLatest { impl -> if (impl != null && impl.hasUserWallets) { @@ -61,9 +64,6 @@ internal class GeneralUserWalletsListManager( } } - override val userWalletsSync: List - get() = requireImplementation.userWalletsSync - override val selectedUserWalletSync: UserWallet? get() = requireImplementation.selectedUserWalletSync diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt index eada356d1e..46d2cbef79 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt @@ -85,7 +85,7 @@ class DefaultTxHistoryRepository( network = network, ) val lastTxHash = walletManager?.wallet?.recentTransactions?.last()?.hash.orEmpty() - return when (val txExploreState = blockchain?.getExploreTxUrl(lastTxHash)) { + return when (val txExploreState = blockchain.getExploreTxUrl(lastTxHash)) { is TxExploreState.Url -> txExploreState.url else -> "" } diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt index d98eb009aa..9b63847e73 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt @@ -18,7 +18,7 @@ interface UserWalletsListManager { /** [Flow] with selected [UserWallet] updates */ val selectedUserWallet: Flow - /** All saved [UserWallet]s */ + /** [List] with all saved [UserWallet]s updates */ val userWalletsSync: List /** Selected [UserWallet] */ diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt index 5d3d60350f..9323fd5423 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetUserWalletUseCase.kt @@ -7,12 +7,11 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.GetUserWalletError import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId -import kotlinx.coroutines.flow.firstOrNull class GetUserWalletUseCase(private val userWalletsListManager: UserWalletsListManager) { - suspend operator fun invoke(userWalletId: UserWalletId): Either = either { - val userWallets = userWalletsListManager.userWallets.firstOrNull().orEmpty() + operator fun invoke(userWalletId: UserWalletId): Either = either { + val userWallets = userWalletsListManager.userWalletsSync ensureNotNull(userWallets.firstOrNull { it.walletId == userWalletId }) { raise(GetUserWalletError.UserWalletNotFound) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index c95d1c7ab7..f40778e2eb 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -41,6 +41,7 @@ import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase @@ -76,7 +77,6 @@ import javax.inject.Inject @HiltViewModel internal class TokenDetailsViewModel @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, - private val getUserWalletUseCase: GetUserWalletUseCase, private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, @@ -103,6 +103,7 @@ internal class TokenDetailsViewModel @Inject constructor( private val analyticsEventsHandler: AnalyticsEventHandler, private val hapticManager: HapticManager, private val clipboardManager: ClipboardManager, + getUserWalletUseCase: GetUserWalletUseCase, featureToggles: TokenDetailsFeatureToggles, deepLinksRegistry: DeepLinksRegistry, savedStateHandle: SavedStateHandle, @@ -115,6 +116,8 @@ internal class TokenDetailsViewModel @Inject constructor( private val cryptoCurrency: CryptoCurrency = savedStateHandle[TokenDetailsRouter.CRYPTO_CURRENCY_KEY] ?: error("This screen can't open without `CryptoCurrency`") + private val userWallet: UserWallet + lateinit var router: InnerTokenDetailsRouter private val marketPriceJobHolder = JobHolder() @@ -174,6 +177,7 @@ internal class TokenDetailsViewModel @Inject constructor( BuyCurrencyDeepLink(::onBuyCurrencyDeepLink), ), ) + userWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: error("UserWallet not found") } private fun onBuyCurrencyDeepLink() { @@ -212,8 +216,7 @@ internal class TokenDetailsViewModel @Inject constructor( .launchIn(viewModelScope) } - private suspend fun updateButtons(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) { - val userWallet = getUserWalletUseCase(userWalletId).getOrElse { return } + private fun updateButtons(currencyStatus: CryptoCurrencyStatus) { getCryptoCurrencyActionsUseCase( userWallet = userWallet, cryptoCurrencyStatus = currencyStatus, @@ -227,12 +230,11 @@ internal class TokenDetailsViewModel @Inject constructor( private fun updateWarnings(cryptoCurrencyStatus: CryptoCurrencyStatus) { viewModelScope.launch(dispatchers.io) { - val wallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch } getCurrencyWarningsUseCase.invoke( userWalletId = userWalletId, currencyStatus = cryptoCurrencyStatus, derivationPath = cryptoCurrency.network.derivationPath, - isSingleWalletWithTokens = wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), + isSingleWalletWithTokens = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), ) .distinctUntilChanged() .onEach { @@ -247,18 +249,17 @@ internal class TokenDetailsViewModel @Inject constructor( private fun subscribeOnCurrencyStatusUpdates() { viewModelScope.launch(dispatchers.io) { - val wallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch } getCurrencyStatusUpdatesUseCase( userWalletId = userWalletId, currencyId = cryptoCurrency.id, - isSingleWalletWithTokens = wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), + isSingleWalletWithTokens = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), ) .distinctUntilChanged() .onEach { maybeCurrencyStatus -> uiState = stateFactory.getCurrencyLoadedBalanceState(maybeCurrencyStatus) maybeCurrencyStatus.onRight { status -> cryptoCurrencyStatus = status - updateButtons(userWalletId = userWalletId, currencyStatus = status) + updateButtons(currencyStatus = status) updateWarnings(status) } currencyStatusAnalyticsSender.send(maybeCurrencyStatus) @@ -355,10 +356,8 @@ internal class TokenDetailsViewModel @Inject constructor( private fun updateTopBarMenu() { viewModelScope.launch(dispatchers.main) { - val wallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch } - uiState = stateFactory.getStateWithUpdatedMenu( - cardTypesResolver = wallet.scanResponse.cardTypesResolver, + cardTypesResolver = userWallet.scanResponse.cardTypesResolver, isBitcoin = isBitcoin(cryptoCurrency.network.id.value), ) } @@ -391,7 +390,7 @@ internal class TokenDetailsViewModel @Inject constructor( viewModelScope.launch(dispatchers.main) { reduxStateHolder.dispatch( TradeCryptoAction.Buy( - userWallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch }, + userWallet = userWallet, cryptoCurrencyStatus = status, appCurrencyCode = selectedAppCurrencyFlow.value.code, ), @@ -428,7 +427,7 @@ internal class TokenDetailsViewModel @Inject constructor( is CryptoCurrency.Coin -> { reduxStateHolder.dispatch( action = TradeCryptoAction.SendCoin( - userWallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch }, + userWallet = userWallet, coinStatus = status, feeCurrencyStatus = maybeFeeCurrencyStatus, transactionInfo = transactionInfo, @@ -454,12 +453,11 @@ internal class TokenDetailsViewModel @Inject constructor( transactionInfo: TransactionInfo?, ) { viewModelScope.launch(dispatchers.io) { - val wallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch } val maybeCoinStatus = getNetworkCoinStatusUseCase( userWalletId = userWalletId, networkId = tokenCurrency.network.id, derivationPath = tokenCurrency.network.derivationPath, - isSingleWalletWithTokens = wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), + isSingleWalletWithTokens = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), ) .conflate() .distinctUntilChanged() @@ -467,7 +465,7 @@ internal class TokenDetailsViewModel @Inject constructor( reduxStateHolder.dispatchWithMain( action = TradeCryptoAction.SendToken( - userWallet = wallet, + userWallet = userWallet, tokenCurrency = tokenCurrency, tokenFiatRate = tokenFiatRate, coinFiatRate = maybeCoinStatus?.fold( @@ -600,9 +598,7 @@ internal class TokenDetailsViewModel @Inject constructor( private fun showErrorIfDemoModeOrElse(action: () -> Unit) { viewModelScope.launch(dispatchers.main) { - val wallet = getUserWalletUseCase(userWalletId = userWalletId).getOrElse { return@launch } - - if (isDemoCardUseCase(cardId = wallet.cardId)) { + if (isDemoCardUseCase(cardId = userWallet.cardId)) { uiState = stateFactory.getStateWithClosedBottomSheet() uiState = stateFactory.getStateAndTriggerEvent( state = uiState, From da98a6afb2143bc1e55b608376766d33c67f8a12 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 May 2024 17:32:37 +0800 Subject: [PATCH 87/98] Updated on 2026-08-14 --- .../feedback/DefaultFeedbackRepository.kt | 32 +++++++++ domain/feedback/build.gradle.kts | 1 + .../domain/feedback/FeedbackDataBuilder.kt | 23 ++++-- .../feedback/SaveBlockchainErrorUseCase.kt | 20 ++++++ .../feedback/models/BlockchainErrorInfo.kt | 22 ++++++ .../feedback/models/FeedbackEmailType.kt | 21 ++++++ .../feedback/repository/FeedbackRepository.kt | 6 ++ .../domain/feedback/utils/AppLogsFormatter.kt | 61 ++++++++++++++++ .../utils/EmailMessageBodyResolver.kt | 70 +++++++++++++++++++ .../utils/EmailMessageTitleResolver.kt | 26 +++++++ .../feedback/utils/EmailSubjectResolver.kt | 33 +++++++++ .../feedback/utils/FeedbackConstants.kt | 6 ++ 12 files changed, 317 insertions(+), 4 deletions(-) create mode 100644 domain/feedback/src/main/java/com/tangem/domain/feedback/SaveBlockchainErrorUseCase.kt create mode 100644 domain/feedback/src/main/java/com/tangem/domain/feedback/models/BlockchainErrorInfo.kt create mode 100644 domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt create mode 100644 domain/feedback/src/main/java/com/tangem/domain/feedback/utils/AppLogsFormatter.kt create mode 100644 domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt create mode 100644 domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt create mode 100644 domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt create mode 100644 domain/feedback/src/main/java/com/tangem/domain/feedback/utils/FeedbackConstants.kt diff --git a/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt b/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt index 9f6c1aa883..4bf2f7eb2a 100644 --- a/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt +++ b/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt @@ -3,6 +3,7 @@ package com.tangem.data.feedback import android.content.Context import android.content.pm.PackageInfo import android.os.Build +import com.tangem.blockchain.common.Blockchain import com.tangem.data.feedback.converters.BlockchainInfoConverter import com.tangem.data.feedback.converters.CardInfoConverter import com.tangem.datasource.local.preferences.AppPreferencesStore @@ -13,6 +14,9 @@ import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.domain.feedback.models.* import com.tangem.domain.feedback.repository.FeedbackRepository import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update import timber.log.Timber import java.io.File import java.io.FileWriter @@ -35,6 +39,8 @@ internal class DefaultFeedbackRepository( private val context: Context, ) : FeedbackRepository { + private val blockchainsErrors = MutableStateFlow>(emptyMap()) + override suspend fun getUserWalletsInfo(): UserWalletsInfo { return UserWalletsInfo( selectedUserWalletId = getSelectedUserWallet().walletId.stringValue, @@ -52,6 +58,16 @@ internal class DefaultFeedbackRepository( .map(BlockchainInfoConverter::convert) } + override suspend fun getBlockchainInfo(blockchainId: String, derivationPath: String?): BlockchainInfo? { + return walletManagersStore + .getSyncOrNull( + userWalletId = getSelectedUserWallet().walletId, + blockchain = Blockchain.fromId(blockchainId), + derivationPath = derivationPath, + ) + ?.let(BlockchainInfoConverter::convert) + } + override fun getPhoneInfo(): PhoneInfo { return PhoneInfo( phoneModel = Build.MODEL, @@ -60,6 +76,22 @@ internal class DefaultFeedbackRepository( ) } + override fun saveBlockchainErrorInfo(error: BlockchainErrorInfo) { + blockchainsErrors.update { + it.toMutableMap().apply { + put(getSelectedUserWallet().walletId, error) + } + } + } + + override suspend fun getBlockchainErrorInfo(): BlockchainErrorInfo? { + return blockchainsErrors.value[getSelectedUserWallet().walletId].also { + if (it == null) { + Timber.e("Blockchain error info is null for ${getSelectedUserWallet().walletId}") + } + } + } + override suspend fun getAppLogs(): List { return appPreferencesStore.getObjectMap(key = PreferencesKeys.APP_LOGS_KEY) .map { AppLogModel(timestamp = it.key.toLong(), message = it.value) } diff --git a/domain/feedback/build.gradle.kts b/domain/feedback/build.gradle.kts index 3e7859b754..6a21560035 100644 --- a/domain/feedback/build.gradle.kts +++ b/domain/feedback/build.gradle.kts @@ -13,4 +13,5 @@ dependencies { implementation(deps.jodatime) implementation(projects.core.res) + implementation(projects.domain.wallets.models) } \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt index 5350902e33..a671bad033 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt @@ -1,9 +1,6 @@ package com.tangem.domain.feedback -import com.tangem.domain.feedback.models.BlockchainInfo -import com.tangem.domain.feedback.models.CardInfo -import com.tangem.domain.feedback.models.PhoneInfo -import com.tangem.domain.feedback.models.UserWalletsInfo +import com.tangem.domain.feedback.models.* import com.tangem.domain.feedback.utils.breakLine import com.tangem.domain.feedback.models.BlockchainInfo.Addresses as BlockchainAddresses @@ -68,6 +65,24 @@ internal class FeedbackDataBuilder { builder.appendKeyValue("App version", phoneInfo.appVersion) } + fun addBlockchainError(info: BlockchainInfo, error: BlockchainErrorInfo) { + builder.appendKeyValue("Blockchain", info.blockchain) + builder.appendKeyValue("Derivation path", info.derivationPath) + builder.appendKeyValue("Host", info.host) + builder.appendKeyValue("Token", error.tokenSymbol) + builder.appendKeyValue("Error", error.errorMessage) + + builder.appendDelimiter() + + builder.appendAddresses( + key = "Source address${info.addresses.isMultiple(suffix = "es")}", + addresses = info.addresses, + ) + builder.appendKeyValue("Destination address", error.destinationAddress) + builder.appendKeyValue("Amount", error.amount) + builder.appendKeyValue("Fee", error.fee ?: "Unable to receive") + } + fun addDelimiter(): StringBuilder = builder.appendDelimiter() fun build(): String = builder.trimEnd().toString() diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/SaveBlockchainErrorUseCase.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/SaveBlockchainErrorUseCase.kt new file mode 100644 index 0000000000..596699a073 --- /dev/null +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/SaveBlockchainErrorUseCase.kt @@ -0,0 +1,20 @@ +package com.tangem.domain.feedback + +import com.tangem.domain.feedback.models.BlockchainErrorInfo +import com.tangem.domain.feedback.repository.FeedbackRepository + +/** + * Save last blockchain error + * + * @property feedbackRepository feedback repository + * +[REDACTED_AUTHOR] + */ +class SaveBlockchainErrorUseCase( + private val feedbackRepository: FeedbackRepository, +) { + + fun invoke(error: BlockchainErrorInfo) { + feedbackRepository.saveBlockchainErrorInfo(error = error) + } +} \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/models/BlockchainErrorInfo.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/BlockchainErrorInfo.kt new file mode 100644 index 0000000000..fc76ba451b --- /dev/null +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/BlockchainErrorInfo.kt @@ -0,0 +1,22 @@ +package com.tangem.domain.feedback.models + +/** + * Information about blockchain's operation error + * + * @property errorMessage message about error + * @property blockchainId blockchain id + * @property derivationPath derivation path + * @property destinationAddress destination address + * @property tokenSymbol token symbol or null, if it isn't operation with token + * @property amount amount + * @property fee fee or null, if unable to get + */ +data class BlockchainErrorInfo( + val errorMessage: String, + val blockchainId: String, + val derivationPath: String?, + val destinationAddress: String, + val tokenSymbol: String?, + val amount: String, + val fee: String?, +) \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt new file mode 100644 index 0000000000..6bbd5a9670 --- /dev/null +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.feedback.models + +/** + * Email feedback type + * +[REDACTED_AUTHOR] + */ +sealed interface FeedbackEmailType { + + /** User initiate request yourself. Example, button on DetailsScreen or OnboardingScreen */ + data object DirectUserRequest : FeedbackEmailType + + /** User rate the app as "can be better" */ + data object RateCanBeBetter : FeedbackEmailType + + /** User has problem with scanning */ + data object ScanningProblem : FeedbackEmailType + + /** User has problem with sending transaction */ + data object TransactionSendingProblem : FeedbackEmailType +} \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/repository/FeedbackRepository.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/repository/FeedbackRepository.kt index 58281067c1..c9d6806eea 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/repository/FeedbackRepository.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/repository/FeedbackRepository.kt @@ -11,8 +11,14 @@ interface FeedbackRepository { suspend fun getBlockchainInfoList(): List + suspend fun getBlockchainInfo(blockchainId: String, derivationPath: String?): BlockchainInfo? + fun getPhoneInfo(): PhoneInfo + fun saveBlockchainErrorInfo(error: BlockchainErrorInfo) + + suspend fun getBlockchainErrorInfo(): BlockchainErrorInfo? + suspend fun getAppLogs(): List suspend fun createLogFile(logs: String): File? diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/AppLogsFormatter.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/AppLogsFormatter.kt new file mode 100644 index 0000000000..e5596b2a78 --- /dev/null +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/AppLogsFormatter.kt @@ -0,0 +1,61 @@ +package com.tangem.domain.feedback.utils + +import com.tangem.domain.feedback.models.AppLogModel +import org.joda.time.DateTime +import org.joda.time.format.DateTimeFormatter +import org.joda.time.format.DateTimeFormatterBuilder +import java.util.Locale + +/** + * App logs formatter + * +[REDACTED_AUTHOR] + */ +internal class AppLogsFormatter { + + private val dateFormatter = createDateFormatter() + + /** Format [appLogs] to [String] */ + fun format(appLogs: List): String { + val builder = StringBuilder() + + var sum = 0 + for (i in appLogs.lastIndex downTo 0) { + val log = appLogs[i] + val date = dateFormatter.print(DateTime(log.timestamp)) + + val formattedLog = "$date: ${log.message}\n" + + sum += formattedLog.length + if (sum < GMAIL_MAX_FILE_SIZE) { + builder.insert(0, formattedLog) + } else { + break + } + } + + return builder.toString() + } + + // Example, 00.00 00:00:00.000 + private fun createDateFormatter(): DateTimeFormatter { + return DateTimeFormatterBuilder() + .appendDayOfMonth(2) + .appendLiteral('.') + .appendMonthOfYear(2) + .appendLiteral(' ') + .appendHourOfDay(2) + .appendLiteral(':') + .appendMinuteOfHour(2) + .appendLiteral(':') + .appendSecondOfMinute(2) + .appendLiteral('.') + .appendMillisOfSecond(MIN_MILLIS_DIGITS) + .toFormatter() + .withLocale(Locale.getDefault()) + } + + private companion object { + const val MIN_MILLIS_DIGITS = 3 + } +} \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt new file mode 100644 index 0000000000..8574fb1010 --- /dev/null +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt @@ -0,0 +1,70 @@ +package com.tangem.domain.feedback.utils + +import com.tangem.domain.feedback.FeedbackDataBuilder +import com.tangem.domain.feedback.models.CardInfo +import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.domain.feedback.repository.FeedbackRepository + +/** + * Email message body resolver + * + * @property feedbackRepository feedback repository + * +[REDACTED_AUTHOR] + */ +internal class EmailMessageBodyResolver( + private val feedbackRepository: FeedbackRepository, +) { + + /** Resolve email message body by [type] using [cardInfo] */ + suspend fun resolve(type: FeedbackEmailType, cardInfo: CardInfo): String = with(FeedbackDataBuilder()) { + when (type) { + FeedbackEmailType.DirectUserRequest -> addUserRequestBody(cardInfo) + FeedbackEmailType.RateCanBeBetter -> addCardAndPhoneInfo(cardInfo) + FeedbackEmailType.ScanningProblem -> addScanningProblemBody() + FeedbackEmailType.TransactionSendingProblem -> addTransactionSendingProblemBody(cardInfo) + } + + return build() + } + + private suspend fun FeedbackDataBuilder.addUserRequestBody(cardInfo: CardInfo) { + addUserWalletsInfo(userWalletsInfo = feedbackRepository.getUserWalletsInfo()) + addDelimiter() + addCardInfo(cardInfo) + addDelimiter() + addBlockchainInfoList(blockchainInfoList = feedbackRepository.getBlockchainInfoList()) + addDelimiter() + addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo()) + } + + private fun FeedbackDataBuilder.addScanningProblemBody() { + addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo()) + } + + private suspend fun FeedbackDataBuilder.addTransactionSendingProblemBody(cardInfo: CardInfo) { + addCardInfo(cardInfo) + addDelimiter() + + val blockchainError = feedbackRepository.getBlockchainErrorInfo() + val blockchainInfo = blockchainError?.let { + feedbackRepository.getBlockchainInfo( + blockchainId = blockchainError.blockchainId, + derivationPath = blockchainError.derivationPath, + ) + } + + if (blockchainInfo != null) { + addBlockchainError(blockchainInfo, blockchainError) + addDelimiter() + } + + addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo()) + } + + private fun FeedbackDataBuilder.addCardAndPhoneInfo(cardInfo: CardInfo) { + addCardInfo(cardInfo) + addDelimiter() + addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo()) + } +} \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt new file mode 100644 index 0000000000..38c983c617 --- /dev/null +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt @@ -0,0 +1,26 @@ +package com.tangem.domain.feedback.utils + +import android.content.res.Resources +import com.tangem.domain.feedback.R +import com.tangem.domain.feedback.models.FeedbackEmailType + +/** + * Email message title resolver + * + * @property resources resources + * +[REDACTED_AUTHOR] + */ +internal class EmailMessageTitleResolver(private val resources: Resources) { + + /** Resolve email message title by [type] */ + fun resolve(type: FeedbackEmailType): String { + return when (type) { + FeedbackEmailType.DirectUserRequest -> R.string.feedback_preface_support + FeedbackEmailType.RateCanBeBetter -> R.string.feedback_preface_rate_negative + FeedbackEmailType.ScanningProblem -> R.string.feedback_preface_scan_failed + FeedbackEmailType.TransactionSendingProblem -> R.string.feedback_preface_tx_failed + } + .let(resources::getString) + } +} \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt new file mode 100644 index 0000000000..392fa7d2f4 --- /dev/null +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt @@ -0,0 +1,33 @@ +package com.tangem.domain.feedback.utils + +import android.content.res.Resources +import com.tangem.domain.feedback.R +import com.tangem.domain.feedback.models.CardInfo +import com.tangem.domain.feedback.models.FeedbackEmailType + +/** + * Email subject resolver + * + * @property resources resources + * +[REDACTED_AUTHOR] + */ +internal class EmailSubjectResolver(private val resources: Resources) { + + /** Resolve email message body by [type] using [cardInfo] */ + fun resolve(type: FeedbackEmailType, cardInfo: CardInfo): String { + return when (type) { + FeedbackEmailType.DirectUserRequest -> { + if (cardInfo.isStart2Coin) { + R.string.feedback_subject_support + } else { + R.string.feedback_subject_support_tangem + } + } + FeedbackEmailType.RateCanBeBetter -> R.string.feedback_subject_rate_negative + FeedbackEmailType.ScanningProblem -> R.string.feedback_subject_scan_failed + FeedbackEmailType.TransactionSendingProblem -> R.string.feedback_subject_tx_failed + } + .let(resources::getString) + } +} \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/FeedbackConstants.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/FeedbackConstants.kt new file mode 100644 index 0000000000..aeaa917e5f --- /dev/null +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/FeedbackConstants.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.feedback.utils + +internal const val GMAIL_MAX_FILE_SIZE = 24_900_000 // ≈ 25 MB + +internal const val START2COIN_SUPPORT_EMAIL = "cardsupport@start2coin.com" +internal const val TANGEM_SUPPORT_EMAIL = "support@tangem.com" \ No newline at end of file From 5c63a8d29cb22a0acc15c940bd3df30c784b5653 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 May 2024 11:08:06 +0500 Subject: [PATCH 88/98] Updated on 2026-08-14 --- .../features/send/impl/presentation/ui/SendNavigationButtons.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index e31aa8a82a..672f385fc0 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -38,7 +38,6 @@ import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.SendUiStateType -import com.tangem.features.send.impl.presentation.utils.getFiatFormatted import com.tangem.features.send.impl.presentation.utils.getFiatString @Composable From a3f1a3dc9fbb06f3348741d19e41ac28ee5967c3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 22 May 2024 15:48:49 +0800 Subject: [PATCH 89/98] Updated on 2026-08-14 --- .../com/tangem/tap/ApplicationEntryPoint.kt | 8 +- .../main/java/com/tangem/tap/MainActivity.kt | 5 + .../java/com/tangem/tap/TangemApplication.kt | 15 ++- .../tap/common/feedback/FeedbackManager.kt | 4 + .../tap/di/domain/FeedbackDomainModule.kt | 15 ++- .../details/ui/details/DetailsFragment.kt | 15 --- .../details/ui/details/DetailsViewModel.kt | 24 +--- .../tap/proxy/redux/DaggerGraphAction.kt | 4 +- .../tap/proxy/redux/DaggerGraphReducer.kt | 1 + .../tap/proxy/redux/DaggerGraphState.kt | 6 +- .../feedback/GetFeedbackEmailUseCase.kt | 67 +++++++++++ .../GetSupportFeedbackEmailUseCase.kt | 113 ------------------ ...pportFeedbackEmail.kt => FeedbackEmail.kt} | 2 +- 13 files changed, 119 insertions(+), 160 deletions(-) create mode 100644 domain/feedback/src/main/java/com/tangem/domain/feedback/GetFeedbackEmailUseCase.kt delete mode 100644 domain/feedback/src/main/java/com/tangem/domain/feedback/GetSupportFeedbackEmailUseCase.kt rename domain/feedback/src/main/java/com/tangem/domain/feedback/models/{SupportFeedbackEmail.kt => FeedbackEmail.kt} (83%) diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index 5099008e5d..83157a1231 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -15,15 +15,17 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.feedback.FeedbackManagerFeatureToggles +import com.tangem.domain.feedback.GetFeedbackEmailUseCase +import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository -import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository +import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository @@ -98,4 +100,8 @@ interface ApplicationEntryPoint { fun getSettingsRepository(): SettingsRepository fun getBlockchainSDKFactory(): BlockchainSDKFactory + + fun getGetFeedbackEmailUseCase(): GetFeedbackEmailUseCase + + fun getSaveBlockchainErrorUseCase(): SaveBlockchainErrorUseCase } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 83dd77c841..78b55f9ecb 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -30,6 +30,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction +import com.tangem.core.navigation.email.EmailSender import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference @@ -151,6 +152,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var userWalletsListManager: UserWalletsListManager + @Inject + lateinit var emailSender: EmailSender + internal val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow @@ -234,6 +238,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac cardSdkConfigRepository = cardSdkConfigRepository, sendRouter = sendRouter, qrScanningRouter = qrScanningRouter, + emailSender = emailSender, ), ) } diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 80c118ae61..d731d06dd8 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -32,15 +32,17 @@ import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.common.LogConfig import com.tangem.domain.feedback.FeedbackManagerFeatureToggles +import com.tangem.domain.feedback.GetFeedbackEmailUseCase +import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository -import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository +import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.tap.common.analytics.AnalyticsFactory @@ -78,6 +80,7 @@ internal lateinit var derivationsFinder: DerivationsFinder abstract class TangemApplication : Application(), ImageLoaderFactory { + // region DI private val entryPoint: ApplicationEntryPoint get() = EntryPoints.get(this, ApplicationEntryPoint::class.java) @@ -171,6 +174,13 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val blockchainSDKFactory: BlockchainSDKFactory get() = entryPoint.getBlockchainSDKFactory() + private val getFeedbackEmailUseCase: GetFeedbackEmailUseCase + get() = entryPoint.getGetFeedbackEmailUseCase() + + private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase + get() = entryPoint.getSaveBlockchainErrorUseCase() + // endregion + override fun onCreate() { super.onCreate() @@ -253,6 +263,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { tangemSdkLogger = tangemSdkLogger, settingsRepository = settingsRepository, blockchainSDKFactory = blockchainSDKFactory, + saveBlockchainErrorUseCase = saveBlockchainErrorUseCase, ), ), ) @@ -344,6 +355,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { infoHolder = additionalFeedbackInfo, logCollector = tangemLogCollector, chatManager = ChatManager(foregroundActivityObserver), + feedbackManagerFeatureToggles = feedbackManagerFeatureToggles, + getFeedbackEmailUseCase = getFeedbackEmailUseCase, ) store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager)) } diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt index 83a14893d8..2f7a4ac1cc 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt @@ -3,6 +3,8 @@ package com.tangem.tap.common.feedback import android.content.Context import com.tangem.datasource.config.models.ChatConfig import com.tangem.domain.common.TapWorkarounds +import com.tangem.domain.feedback.FeedbackManagerFeatureToggles +import com.tangem.domain.feedback.GetFeedbackEmailUseCase import com.tangem.tap.common.chat.ChatManager import com.tangem.tap.common.extensions.sendEmail import com.tangem.tap.common.log.TangemLogCollector @@ -20,6 +22,8 @@ class FeedbackManager( val infoHolder: AdditionalFeedbackInfo, private val logCollector: TangemLogCollector, private val chatManager: ChatManager, + @Suppress("UnusedPrivateMember") private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles, + @Suppress("UnusedPrivateMember") private val getFeedbackEmailUseCase: GetFeedbackEmailUseCase, ) { private var sessionFeedbackFile: File? = null diff --git a/app/src/main/java/com/tangem/tap/di/domain/FeedbackDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/FeedbackDomainModule.kt index f2aea82f57..033e322536 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/FeedbackDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/FeedbackDomainModule.kt @@ -1,7 +1,8 @@ package com.tangem.tap.di.domain import android.content.Context -import com.tangem.domain.feedback.GetSupportFeedbackEmailUseCase +import com.tangem.domain.feedback.GetFeedbackEmailUseCase +import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.repository.FeedbackRepository import dagger.Module import dagger.Provides @@ -16,10 +17,16 @@ internal object FeedbackDomainModule { @Provides @Singleton - fun provideGetFeedbackToSupportUseCase( + fun provideGetFeedbackEmailUseCase( feedbackRepository: FeedbackRepository, @ApplicationContext context: Context, - ): GetSupportFeedbackEmailUseCase { - return GetSupportFeedbackEmailUseCase(feedbackRepository = feedbackRepository, resources = context.resources) + ): GetFeedbackEmailUseCase { + return GetFeedbackEmailUseCase(feedbackRepository = feedbackRepository, resources = context.resources) + } + + @Provides + @Singleton + fun provideSaveBlockchainErrorUseCase(feedbackRepository: FeedbackRepository): SaveBlockchainErrorUseCase { + return SaveBlockchainErrorUseCase(feedbackRepository = feedbackRepository) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt index 46a8f606ff..a382e52b7f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt @@ -5,11 +5,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.NavigationAction -import com.tangem.core.navigation.email.EmailSender import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.domain.feedback.FeedbackManagerFeatureToggles -import com.tangem.domain.feedback.GetSupportFeedbackEmailUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.tap.common.analytics.events.Settings @@ -28,15 +25,6 @@ internal class DetailsFragment : ComposeFragment(), StoreSubscriber, private val walletsRepository: WalletsRepository, - private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles, - private val getSupportFeedbackEmailUseCase: GetSupportFeedbackEmailUseCase, - private val emailSender: EmailSender, private val userWalletsListManager: UserWalletsListManager, ) { @@ -149,21 +141,7 @@ internal class DetailsViewModel( private fun sendFeedback() { Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Settings)) - if (feedbackManagerFeatureToggles.isLocalLogsEnabled) { - mainScope.launch { - val email = getSupportFeedbackEmailUseCase() - emailSender.send( - email = EmailSender.Email( - address = email.address, - subject = email.subject, - message = email.message, - attachment = email.file, - ), - ) - } - } else { - store.dispatchOnMain(GlobalAction.SendEmail(FeedbackEmail())) - } + store.dispatchOnMain(GlobalAction.SendEmail(FeedbackEmail())) } private fun navigateToAppSettings() { diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt index 995658a42c..f854ba0dae 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt @@ -1,8 +1,9 @@ package com.tangem.tap.proxy.redux -import com.tangem.feature.qrscanning.QrScanningRouter +import com.tangem.core.navigation.email.EmailSender import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.features.managetokens.navigation.ManageTokensUi import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.tester.api.TesterRouter @@ -23,5 +24,6 @@ sealed interface DaggerGraphAction : Action { val cardSdkConfigRepository: CardSdkConfigRepository, val sendRouter: SendRouter, val qrScanningRouter: QrScanningRouter, + val emailSender: EmailSender, ) : DaggerGraphAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt index 7074523df0..b25fdce12b 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt @@ -22,6 +22,7 @@ object DaggerGraphReducer { cardSdkConfigRepository = action.cardSdkConfigRepository, sendRouter = action.sendRouter, qrScanningRouter = action.qrScanningRouter, + emailSender = action.emailSender, ) } } diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 0c09261f91..ab600ee8ea 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -2,6 +2,7 @@ package com.tangem.tap.proxy.redux import com.tangem.TangemSdkLogger import com.tangem.blockchainsdk.BlockchainSDKFactory +import com.tangem.core.navigation.email.EmailSender import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.apptheme.repository.AppThemeModeRepository @@ -11,15 +12,16 @@ import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.feedback.FeedbackManagerFeatureToggles +import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository -import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository +import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles import com.tangem.features.managetokens.navigation.ManageTokensUi @@ -69,4 +71,6 @@ data class DaggerGraphState( val tangemSdkLogger: TangemSdkLogger? = null, val settingsRepository: SettingsRepository? = null, val blockchainSDKFactory: BlockchainSDKFactory? = null, + val emailSender: EmailSender? = null, + val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase? = null, ) : StateType \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/GetFeedbackEmailUseCase.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/GetFeedbackEmailUseCase.kt new file mode 100644 index 0000000000..50998a4ed8 --- /dev/null +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/GetFeedbackEmailUseCase.kt @@ -0,0 +1,67 @@ +package com.tangem.domain.feedback + +import android.content.res.Resources +import com.tangem.domain.feedback.models.CardInfo +import com.tangem.domain.feedback.models.FeedbackEmail +import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.domain.feedback.repository.FeedbackRepository +import com.tangem.domain.feedback.utils.* + +/** + * Get email with feedback for support + * + * @property feedbackRepository feedback repository + * @property resources resources for getting strings + * +[REDACTED_AUTHOR] + */ +class GetFeedbackEmailUseCase( + private val feedbackRepository: FeedbackRepository, + private val resources: Resources, +) { + + private val emailSubjectResolver = EmailSubjectResolver(resources) + private val emailMessageTitleResolver = EmailMessageTitleResolver(resources) + private val emailMessageBodyResolver = EmailMessageBodyResolver(feedbackRepository) + + suspend operator fun invoke(type: FeedbackEmailType): FeedbackEmail { + val cardInfo = feedbackRepository.getCardInfo() + + val formattedLogs = AppLogsFormatter().format(appLogs = feedbackRepository.getAppLogs()) + + return FeedbackEmail( + address = getAddress(cardInfo), + subject = emailSubjectResolver.resolve(type, cardInfo), + message = createMessage(type, cardInfo), + file = feedbackRepository.createLogFile(logs = formattedLogs), + ) + } + + private fun getAddress(cardInfo: CardInfo): String { + return if (cardInfo.isStart2Coin) START2COIN_SUPPORT_EMAIL else TANGEM_SUPPORT_EMAIL + } + + private suspend fun createMessage(type: FeedbackEmailType, cardInfo: CardInfo): String { + return StringBuilder().apply { + val title = emailMessageTitleResolver.resolve(type) + append(title) + + skipLine() + + appendDisclaimerIfNeeded(type) + + skipLine() + + val body = emailMessageBodyResolver.resolve(type, cardInfo) + append(body) + }.toString() + } + + private fun StringBuilder.appendDisclaimerIfNeeded(type: FeedbackEmailType): StringBuilder { + return if (type is FeedbackEmailType.ScanningProblem) { + this + } else { + append(resources.getString(R.string.feedback_data_collection_message)) + } + } +} \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/GetSupportFeedbackEmailUseCase.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/GetSupportFeedbackEmailUseCase.kt deleted file mode 100644 index 6ab491c734..0000000000 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/GetSupportFeedbackEmailUseCase.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.tangem.domain.feedback - -import android.content.res.Resources -import com.tangem.domain.feedback.models.CardInfo -import com.tangem.domain.feedback.models.SupportFeedbackEmail -import com.tangem.domain.feedback.repository.FeedbackRepository -import com.tangem.domain.feedback.utils.skipLine -import org.joda.time.DateTime -import org.joda.time.format.DateTimeFormatterBuilder -import java.util.Locale - -/** - * Get email with feedback for support - * - * @property feedbackRepository feedback repository - * @property resources resources for getting strings - * -[REDACTED_AUTHOR] - */ -class GetSupportFeedbackEmailUseCase( - private val feedbackRepository: FeedbackRepository, - private val resources: Resources, -) { - - // 00.00 00:00:00.000 - private val dateFormatter = DateTimeFormatterBuilder() - .appendDayOfMonth(2) - .appendLiteral('.') - .appendMonthOfYear(2) - .appendLiteral(' ') - .appendHourOfDay(2) - .appendLiteral(':') - .appendMinuteOfHour(2) - .appendLiteral(':') - .appendSecondOfMinute(2) - .appendLiteral('.') - .appendMillisOfSecond(3) - .toFormatter() - .withLocale(Locale.getDefault()) - - suspend operator fun invoke(): SupportFeedbackEmail { - val cardInfo = feedbackRepository.getCardInfo() - - return SupportFeedbackEmail( - address = getEmail(isStart2Coin = cardInfo.isStart2Coin), - subject = getSubject(isStart2Coin = cardInfo.isStart2Coin), - message = createMessage(cardInfo), - file = feedbackRepository.createLogFile(logs = getLogs()), - ) - } - - private fun getEmail(isStart2Coin: Boolean): String { - return if (isStart2Coin) START2COIN_SUPPORT_EMAIL else TANGEM_SUPPORT_EMAIL - } - - private fun getSubject(isStart2Coin: Boolean): String { - return resources.getString( - if (isStart2Coin) { - R.string.feedback_subject_support - } else { - R.string.feedback_subject_support_tangem - }, - ) - } - - private suspend fun createMessage(cardInfo: CardInfo): String { - return StringBuilder().apply { - append(resources.getString(R.string.feedback_preface_support)) - skipLine() - append(resources.getString(R.string.feedback_data_collection_message)) - skipLine() - append( - FeedbackDataBuilder().apply { - addUserWalletsInfo(userWalletsInfo = feedbackRepository.getUserWalletsInfo()) - addDelimiter() - addCardInfo(cardInfo) - addDelimiter() - addBlockchainInfoList(blockchainInfoList = feedbackRepository.getBlockchainInfoList()) - addDelimiter() - addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo()) - }.build(), - ) - }.toString() - } - - private suspend fun getLogs(): String { - val builder = StringBuilder() - - var sum = 0 - val appLogs = feedbackRepository.getAppLogs() - for (i in appLogs.lastIndex downTo 0) { - val log = appLogs[i] - val date = dateFormatter.print(DateTime(log.timestamp)) - - val formattedLog = "$date: ${log.message}\n" - - sum += formattedLog.length - if (sum < GMAIL_MAX_FILE_SIZE) { - builder.insert(0, formattedLog) - } else { - break - } - } - - return builder.toString() - } - - private companion object { - const val START2COIN_SUPPORT_EMAIL = "cardsupport@start2coin.com" - const val TANGEM_SUPPORT_EMAIL = "support@tangem.com" - const val GMAIL_MAX_FILE_SIZE = 24_900_000 // ≈ 25 MB - } -} \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/models/SupportFeedbackEmail.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmail.kt similarity index 83% rename from domain/feedback/src/main/java/com/tangem/domain/feedback/models/SupportFeedbackEmail.kt rename to domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmail.kt index 35faf18f6f..9187f590f0 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/models/SupportFeedbackEmail.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmail.kt @@ -2,7 +2,7 @@ package com.tangem.domain.feedback.models import java.io.File -data class SupportFeedbackEmail( +data class FeedbackEmail( val address: String, val subject: String, val message: String, From d630852c2939f107db518acaff99343f6c5f0bd0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 May 2024 14:02:59 +0500 Subject: [PATCH 90/98] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 2 + .../tap/di/domain/TransactionDomainModule.kt | 18 ++++++ domain/legacy/build.gradle.kts | 1 + .../DefaultWalletManagersFacade.kt | 32 ++++++++++ .../walletmanager/WalletManagersFacade.kt | 13 ++++ .../utils/CryptoCurrencyTypeConverter.kt | 23 +++++++ .../SdkRequirementsConditionConverter.kt | 18 ++++++ domain/tokens/build.gradle.kts | 1 + .../tokens/model/warnings/HederaWarnings.kt | 18 ++++++ .../analytics/TokenScreenAnalyticsEvent.kt | 5 ++ .../tokens/GetCryptoCurrencyActionsUseCase.kt | 30 +++++++-- .../tokens/GetCurrencyWarningsUseCase.kt | 19 ++++++ .../model/ScenarioUnavailabilityReason.kt | 2 + domain/transaction/models/.gitignore | 1 + domain/transaction/models/build.gradle.kts | 4 ++ .../models/AssetRequirementsCondition.kt | 20 ++++++ .../transaction/error/AssociateAssetError.kt | 9 +++ .../usecase/AssociateAssetUseCase.kt | 63 +++++++++++++++++++ features/tokendetails/impl/build.gradle.kts | 1 + ...okenDetailsNotificationsAnalyticsSender.kt | 1 + .../components/TokenDetailsDialogConfig.kt | 17 +++++ .../components/TokenDetailsNotification.kt | 25 +++++++- .../TokenDetailsNotificationConverter.kt | 25 ++++++++ .../state/factory/TokenDetailsStateFactory.kt | 26 +++++++- .../viewmodels/TokenDetailsClickIntents.kt | 2 + .../viewmodels/TokenDetailsViewModel.kt | 36 ++++++++++- .../WalletCurrencyActionsClickIntents.kt | 3 + settings.gradle.kts | 1 + 28 files changed, 406 insertions(+), 10 deletions(-) create mode 100644 domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/CryptoCurrencyTypeConverter.kt create mode 100644 domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkRequirementsConditionConverter.kt create mode 100644 domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/HederaWarnings.kt create mode 100644 domain/transaction/models/.gitignore create mode 100644 domain/transaction/models/build.gradle.kts create mode 100644 domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/models/AssetRequirementsCondition.kt create mode 100644 domain/transaction/src/main/java/com/tangem/domain/transaction/error/AssociateAssetError.kt create mode 100644 domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 91c7d4aadb..0067fa4c3a 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -185,6 +185,7 @@ internal object TokensDomainModule { @ViewModelScoped fun provideGetCryptoCurrencyActionsUseCase( rampStateManager: RampStateManager, + walletManagersFacade: WalletManagersFacade, marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository, currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, @@ -193,6 +194,7 @@ internal object TokensDomainModule { ): GetCryptoCurrencyActionsUseCase { return GetCryptoCurrencyActionsUseCase( rampManager = rampStateManager, + walletManagersFacade = walletManagersFacade, marketCryptoCurrencyRepository = marketCryptoCurrencyRepository, currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index 0066c2ae37..bb6a62fc78 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -2,6 +2,8 @@ package com.tangem.tap.di.domain import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.transaction.usecase.* @@ -40,6 +42,22 @@ internal object TransactionDomainModule { ) } + @Provides + @ViewModelScoped + fun provideAssociateAssetUseCase( + cardSdkConfigRepository: CardSdkConfigRepository, + walletManagersFacade: WalletManagersFacade, + currenciesRepository: CurrenciesRepository, + networksRepository: NetworksRepository, + ): AssociateAssetUseCase { + return AssociateAssetUseCase( + cardSdkConfigRepository = cardSdkConfigRepository, + walletManagersFacade = walletManagersFacade, + currenciesRepository = currenciesRepository, + networksRepository = networksRepository, + ) + } + @Provides @ViewModelScoped fun provideCreateTransactionUseCase(transactionRepository: TransactionRepository): CreateTransactionUseCase { diff --git a/domain/legacy/build.gradle.kts b/domain/legacy/build.gradle.kts index f13641bdc1..f01a2e6a96 100644 --- a/domain/legacy/build.gradle.kts +++ b/domain/legacy/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { implementation(projects.domain.demo) implementation(projects.domain.models) implementation(projects.domain.tokens.models) + implementation(projects.domain.transaction.models) implementation(projects.domain.txhistory.models) implementation(projects.domain.wallets.models) /** Tangem libraries */ diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 1f8a476da0..c63725cdc2 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -13,6 +13,7 @@ import com.tangem.blockchain.common.address.EstimationFeeAddressFactory import com.tangem.blockchain.common.pagination.Page import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.blockchain.common.trustlines.AssetRequirementsManager import com.tangem.blockchain.common.txhistory.TransactionHistoryRequest import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult @@ -26,6 +27,7 @@ import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning +import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.domain.txhistory.models.PaginationWrapper import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryState @@ -57,6 +59,8 @@ class DefaultWalletManagersFacade( private val txHistoryStateConverter by lazy { SdkTransactionHistoryStateConverter() } private val txHistoryItemConverter by lazy { SdkTransactionHistoryItemConverter(assetReader, moshi) } private val sdkPageConverter by lazy { SdkPageConverter() } + private val cryptoCurrencyTypeConverter by lazy { CryptoCurrencyTypeConverter() } + private val requirementsConditionConverter by lazy { SdkRequirementsConditionConverter() } private val estimationFeeAddressFactory by lazy { EstimationFeeAddressFactory() } override suspend fun update( @@ -575,6 +579,34 @@ class DefaultWalletManagersFacade( ) } + override suspend fun getAssetRequirements( + userWalletId: UserWalletId, + currency: CryptoCurrency, + ): AssetRequirementsCondition? { + val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network) + val currencyType = cryptoCurrencyTypeConverter.convert(currency) + if (walletManager !is AssetRequirementsManager || !walletManager.hasRequirements(currencyType)) return null + + val condition = walletManager.requirementsCondition(currencyType) ?: return null + return requirementsConditionConverter.convert(condition) + } + + override suspend fun associateAsset( + userWalletId: UserWalletId, + currency: CryptoCurrency, + signer: CommonSigner, + ): SimpleResult { + val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network) + val currencyType = cryptoCurrencyTypeConverter.convert(currency) + + if (walletManager !is AssetRequirementsManager) { + return SimpleResult.Failure( + BlockchainSdkError.CustomError("WalletManager is not implemented AssetRequirementsManager"), + ) + } + return walletManager.fulfillRequirements(currencyType, signer) + } + private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set) { if (tokens.isEmpty()) return diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt index 4636317765..5c80c0db3d 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt @@ -13,6 +13,7 @@ import com.tangem.blockchain.extensions.SimpleResult import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning +import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.domain.txhistory.models.PaginationWrapper import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryState @@ -240,4 +241,16 @@ interface WalletManagersFacade { decimals: Int, id: String? = null, ): BigDecimal + + /** + * Get requirements for asset(currency) + * @return null if there's no requirement, otherwise [AssetRequirementsCondition]. + */ + suspend fun getAssetRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): AssetRequirementsCondition? + + suspend fun associateAsset( + userWalletId: UserWalletId, + currency: CryptoCurrency, + signer: CommonSigner, + ): SimpleResult } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/CryptoCurrencyTypeConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/CryptoCurrencyTypeConverter.kt new file mode 100644 index 0000000000..53d7f303c5 --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/CryptoCurrencyTypeConverter.kt @@ -0,0 +1,23 @@ +package com.tangem.domain.walletmanager.utils + +import com.tangem.blockchain.common.CryptoCurrencyType +import com.tangem.blockchain.common.Token +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.utils.converter.Converter + +internal class CryptoCurrencyTypeConverter : Converter { + override fun convert(value: CryptoCurrency): CryptoCurrencyType { + return when (value) { + is CryptoCurrency.Coin -> CryptoCurrencyType.Coin + is CryptoCurrency.Token -> CryptoCurrencyType.Token( + info = Token( + name = value.name, + symbol = value.symbol, + contractAddress = value.contractAddress, + decimals = value.decimals, + id = value.id.rawCurrencyId, + ), + ) + } + } +} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkRequirementsConditionConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkRequirementsConditionConverter.kt new file mode 100644 index 0000000000..7c84120470 --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkRequirementsConditionConverter.kt @@ -0,0 +1,18 @@ +package com.tangem.domain.walletmanager.utils + +import com.tangem.domain.transaction.models.AssetRequirementsCondition +import com.tangem.utils.converter.Converter +import com.tangem.blockchain.common.trustlines.AssetRequirementsCondition as SdkRequirementsCondition + +internal class SdkRequirementsConditionConverter : Converter { + override fun convert(value: SdkRequirementsCondition): AssetRequirementsCondition { + return when (value) { + SdkRequirementsCondition.PaidTransaction -> AssetRequirementsCondition.PaidTransaction + is SdkRequirementsCondition.PaidTransactionWithFee -> AssetRequirementsCondition.PaidTransactionWithFee( + feeAmount = requireNotNull(value.feeAmount.value), + feeCurrencySymbol = value.feeAmount.currencySymbol, + decimals = value.feeAmount.decimals, + ) + } + } +} \ No newline at end of file diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts index 501cf70dd8..de3ef835c5 100644 --- a/domain/tokens/build.gradle.kts +++ b/domain/tokens/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { implementation(projects.libs.blockchainSdk) implementation(projects.domain.tokens.models) implementation(projects.domain.txhistory.models) + implementation(projects.domain.transaction.models) implementation(projects.domain.wallets.models) implementation(projects.domain.appCurrency.models) implementation(projects.domain.settings) diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/HederaWarnings.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/HederaWarnings.kt new file mode 100644 index 0000000000..e251aae9a0 --- /dev/null +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/HederaWarnings.kt @@ -0,0 +1,18 @@ +package com.tangem.domain.tokens.model.warnings + +import com.tangem.domain.tokens.model.CryptoCurrency +import java.math.BigDecimal + +sealed class HederaWarnings : CryptoCurrencyWarning() { + + abstract val currency: CryptoCurrency + + data class AssociateWarning(override val currency: CryptoCurrency) : HederaWarnings() + + data class AssociateWarningWithFee( + override val currency: CryptoCurrency, + val fee: BigDecimal, + val feeCurrencySymbol: String, + val feeCurrencyDecimals: Int, + ) : HederaWarnings() +} \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/analytics/TokenScreenAnalyticsEvent.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/analytics/TokenScreenAnalyticsEvent.kt index ed01f84f51..171f60eb9e 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/analytics/TokenScreenAnalyticsEvent.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/analytics/TokenScreenAnalyticsEvent.kt @@ -72,4 +72,9 @@ sealed class TokenScreenAnalyticsEvent( event = "Token Bought", params = mapOf("Token" to token), ) + + class Associate(tokenSymbol: String, blockchain: String) : TokenScreenAnalyticsEvent( + event = "Button - Token Trustline", + params = mapOf("Token" to tokenSymbol, "Blockchain" to blockchain), + ) } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index d0155c6c62..e521d035af 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -8,6 +8,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWallet import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.isNullOrZero @@ -22,6 +23,7 @@ import kotlinx.coroutines.flow.* @Suppress("LongParameterList") class GetCryptoCurrencyActionsUseCase( private val rampManager: RampStateManager, + private val walletManagersFacade: WalletManagersFacade, private val marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository, private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, @@ -30,7 +32,10 @@ class GetCryptoCurrencyActionsUseCase( ) { @OptIn(ExperimentalCoroutinesApi::class) - operator fun invoke(userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus): Flow { + suspend operator fun invoke( + userWallet: UserWallet, + cryptoCurrencyStatus: CryptoCurrencyStatus, + ): Flow { val operations = CurrenciesStatusesOperations( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, @@ -38,7 +43,7 @@ class GetCryptoCurrencyActionsUseCase( userWalletId = userWallet.walletId, ) val networkId = cryptoCurrencyStatus.currency.network.id - + val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, cryptoCurrencyStatus.currency) return flow { val networkFlow = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) { operations.getNetworkCoinForSingleWalletWithTokenFlow(networkId) @@ -53,6 +58,7 @@ class GetCryptoCurrencyActionsUseCase( userWallet = userWallet, coinStatus = maybeCoinStatus.getOrNull(), cryptoCurrencyStatus = cryptoCurrencyStatus, + needAssociateAsset = requirements != null, ) } @@ -64,6 +70,7 @@ class GetCryptoCurrencyActionsUseCase( userWallet: UserWallet, coinStatus: CryptoCurrencyStatus?, cryptoCurrencyStatus: CryptoCurrencyStatus, + needAssociateAsset: Boolean, ): TokenActionsState { return TokenActionsState( walletId = userWallet.walletId, @@ -72,6 +79,7 @@ class GetCryptoCurrencyActionsUseCase( userWallet = userWallet, coinStatus = coinStatus, cryptoCurrencyStatus = cryptoCurrencyStatus, + needAssociateAsset = needAssociateAsset, ), ) } @@ -85,13 +93,14 @@ class GetCryptoCurrencyActionsUseCase( userWallet: UserWallet, coinStatus: CryptoCurrencyStatus?, cryptoCurrencyStatus: CryptoCurrencyStatus, + needAssociateAsset: Boolean, ): List { val cryptoCurrency = cryptoCurrencyStatus.currency if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.MissedDerivation) { return listOf(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None)) } if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Unreachable) { - return getActionsForUnreachableCurrency(cryptoCurrencyStatus) + return getActionsForUnreachableCurrency(cryptoCurrencyStatus, needAssociateAsset) } val activeList = mutableListOf() @@ -104,7 +113,12 @@ class GetCryptoCurrencyActionsUseCase( // receive if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { - activeList.add(TokenActionsState.ActionState.Receive(ScenarioUnavailabilityReason.None)) + val scenario = if (needAssociateAsset) { + ScenarioUnavailabilityReason.UnassociatedAsset + } else { + ScenarioUnavailabilityReason.None + } + activeList.add(TokenActionsState.ActionState.Receive(scenario)) } // send @@ -190,6 +204,7 @@ class GetCryptoCurrencyActionsUseCase( private fun getActionsForUnreachableCurrency( cryptoCurrencyStatus: CryptoCurrencyStatus, + needAssociateAsset: Boolean, ): List { val actionsList = mutableListOf() @@ -212,7 +227,12 @@ class GetCryptoCurrencyActionsUseCase( actionsList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.Unreachable)) if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { - actionsList.add(TokenActionsState.ActionState.Receive(ScenarioUnavailabilityReason.None)) + val scenario = if (needAssociateAsset) { + ScenarioUnavailabilityReason.UnassociatedAsset + } else { + ScenarioUnavailabilityReason.None + } + actionsList.add(TokenActionsState.ActionState.Receive(scenario)) } actionsList.add(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None)) return actionsList diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index 5036e8f341..ba46c3aca0 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -6,8 +6,10 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.FeePaidCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning +import com.tangem.domain.tokens.model.warnings.HederaWarnings import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations import com.tangem.domain.tokens.repository.* +import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.domain.api.SwapRepository @@ -76,6 +78,7 @@ class GetCurrencyWarningsUseCase( getNetworkUnavailableWarning(currencyStatus), getNetworkNoAccountWarning(currencyStatus), getBeaconChainShutdownWarning(currency.network.id), + getAssetRequirementsWarning(userWalletId = userWalletId, currency = currency), ) }.flowOn(dispatchers.io) } @@ -267,6 +270,22 @@ class GetCurrencyWarningsUseCase( return if (BlockchainUtils.isBeaconChain(networkId.value)) CryptoCurrencyWarning.BeaconChainShutdown else null } + private suspend fun getAssetRequirementsWarning( + userWalletId: UserWalletId, + currency: CryptoCurrency, + ): CryptoCurrencyWarning? { + return when (val requirements = walletManagersFacade.getAssetRequirements(userWalletId, currency)) { + is AssetRequirementsCondition.PaidTransaction -> HederaWarnings.AssociateWarning(currency = currency) + is AssetRequirementsCondition.PaidTransactionWithFee -> HederaWarnings.AssociateWarningWithFee( + currency = currency, + fee = requirements.feeAmount, + feeCurrencySymbol = requirements.feeCurrencySymbol, + feeCurrencyDecimals = requirements.decimals, + ) + null -> null + } + } + private fun BigDecimal?.isZero(): Boolean { return this?.signum() == 0 } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt index e241178026..54db16bf92 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt @@ -21,6 +21,8 @@ sealed class ScenarioUnavailabilityReason { data object Unreachable : ScenarioUnavailabilityReason() + data object UnassociatedAsset : ScenarioUnavailabilityReason() + enum class WithdrawalScenario { SELL, SEND } diff --git a/domain/transaction/models/.gitignore b/domain/transaction/models/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/domain/transaction/models/.gitignore @@ -0,0 +1 @@ +/build diff --git a/domain/transaction/models/build.gradle.kts b/domain/transaction/models/build.gradle.kts new file mode 100644 index 0000000000..7ff7fb7522 --- /dev/null +++ b/domain/transaction/models/build.gradle.kts @@ -0,0 +1,4 @@ +plugins { + alias(deps.plugins.kotlin.jvm) + id("configuration") +} \ No newline at end of file diff --git a/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/models/AssetRequirementsCondition.kt b/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/models/AssetRequirementsCondition.kt new file mode 100644 index 0000000000..3503f354df --- /dev/null +++ b/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/models/AssetRequirementsCondition.kt @@ -0,0 +1,20 @@ +package com.tangem.domain.transaction.models + +import java.math.BigDecimal + +sealed class AssetRequirementsCondition { + + /** + * The exact value of the fee for this type of condition is unknown. + */ + data object PaidTransaction : AssetRequirementsCondition() + + /** + * The exact value of the fee for this type of condition is stored in `feeAmount`. + */ + data class PaidTransactionWithFee( + val feeAmount: BigDecimal, + val feeCurrencySymbol: String, + val decimals: Int, + ) : AssetRequirementsCondition() +} \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/AssociateAssetError.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/AssociateAssetError.kt new file mode 100644 index 0000000000..fc92dd8cea --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/AssociateAssetError.kt @@ -0,0 +1,9 @@ +package com.tangem.domain.transaction.error + +import com.tangem.domain.tokens.model.CryptoCurrency + +sealed class AssociateAssetError { + data class NotEnoughBalance(val feeCurrency: CryptoCurrency) : AssociateAssetError() + + data class DataError(val message: String?) : AssociateAssetError() +} \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt new file mode 100644 index 0000000000..3187c90352 --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt @@ -0,0 +1,63 @@ +package com.tangem.domain.transaction.usecase + +import arrow.core.Either +import arrow.core.raise.catch +import arrow.core.raise.either +import com.tangem.blockchain.extensions.SimpleResult +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyAmountStatus +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.transaction.error.AssociateAssetError +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.isNullOrZero + +class AssociateAssetUseCase( + private val cardSdkConfigRepository: CardSdkConfigRepository, + private val walletManagersFacade: WalletManagersFacade, + private val currenciesRepository: CurrenciesRepository, + private val networksRepository: NetworksRepository, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + currency: CryptoCurrency, + ): Either { + return either { + val networkCoin = currenciesRepository.getNetworkCoin( + userWalletId = userWalletId, + networkId = currency.network.id, + derivationPath = currency.network.derivationPath, + ) + if (isBalanceZero(userWalletId, networkCoin)) { + raise(AssociateAssetError.NotEnoughBalance(networkCoin)) + } + val signer = cardSdkConfigRepository.getCommonSigner(cardId = null) + + catch( + block = { + when (val result = walletManagersFacade.associateAsset(userWalletId, currency, signer)) { + is SimpleResult.Failure -> raise(AssociateAssetError.DataError(result.error.message)) + SimpleResult.Success -> Unit + } + }, + catch = { error -> AssociateAssetError.DataError(error.message) }, + ) + } + } + + private suspend fun isBalanceZero(userWalletId: UserWalletId, currency: CryptoCurrency): Boolean { + val networkStatus = networksRepository.getNetworkStatusesSync( + userWalletId = userWalletId, + networks = setOf(currency.network), + ).find { it.network == currency.network } + val networkCoinAmountStatus = (networkStatus?.value as? NetworkStatus.Verified) + ?.amounts + ?.get(currency.id) + return networkCoinAmountStatus is CryptoCurrencyAmountStatus.Loaded && + networkCoinAmountStatus.value.isNullOrZero() + } +} \ No newline at end of file diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index 0ec7c8726f..5c435284d7 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -74,6 +74,7 @@ dependencies { implementation(projects.domain.wallets.models) implementation(projects.domain.balanceHiding) implementation(projects.domain.balanceHiding.models) + implementation(projects.domain.transaction) /** Temp dependency to swap domain */ implementation(projects.features.swap.domain) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt index b458c3f622..998dbf0aff 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt @@ -42,6 +42,7 @@ internal class TokenDetailsNotificationsAnalyticsSender( is TokenDetailsNotification.RentInfo, is TokenDetailsNotification.SwapPromo, is TokenDetailsNotification.NetworkShutdown, + is TokenDetailsNotification.HederaAssociateWarning, -> null } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt index f0bccdec33..dfa224df70 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt @@ -94,5 +94,22 @@ internal data class TokenDetailsDialogConfig( onClick = onConfirmClick, ) } + + data class ErrorDialogConfig( + val text: TextReference, + val onConfirmClick: () -> Unit, + ) : DialogContentConfig() { + + override val title = null + + override val message: TextReference = text + + override val cancelButtonConfig = null + + override val confirmButtonConfig: ButtonConfig = ButtonConfig( + text = TextReference.Res(R.string.common_ok), + onClick = onConfirmClick, + ) + } } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index 853191b4a2..c6314d4e3f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -117,8 +117,7 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { ), ), iconResId = currency.networkIconResId, - buttonsState = - NotificationConfig.ButtonsState.SecondaryButtonConfig( + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( text = resourceReference( id = R.string.common_buy_currency, formatArgs = wrappedList( @@ -176,4 +175,26 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { title = title, subtitle = subtitle, ) + + data class HederaAssociateWarning( + private val currency: CryptoCurrency, + private val fee: String?, + private val feeCurrencySymbol: String?, + private val onAssociateClick: () -> Unit, + ) : Warning( + title = resourceReference(R.string.warning_hedera_missing_token_association_title), + subtitle = if (fee != null && feeCurrencySymbol != null) { + resourceReference( + id = R.string.warning_hedera_missing_token_association_message, + formatArgs = wrappedList(fee, feeCurrencySymbol), + ) + } else { + resourceReference(R.string.warning_hedera_missing_token_association_message_brief) + }, + iconResId = currency.networkIconResId, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.warning_hedera_missing_token_association_button_title), + onClick = onAssociateClick, + ), + ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index 8f368bb1b3..33c5b85d67 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -6,6 +6,7 @@ import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning +import com.tangem.domain.tokens.model.warnings.HederaWarnings import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification.* @@ -30,6 +31,13 @@ internal class TokenDetailsNotificationConverter( return newNotifications.toImmutableList() } + fun removeHederaAssociateWarning(currentState: TokenDetailsState): ImmutableList { + val newNotifications = currentState.notifications.toMutableList() + newNotifications.removeBy { it is HederaAssociateWarning } + return newNotifications.toImmutableList() + } + + @Suppress("LongMethod") private fun mapToNotification(warning: CryptoCurrencyWarning): TokenDetailsNotification { return when (warning) { is CryptoCurrencyWarning.BalanceNotEnoughForFee -> NetworkFeeWithBuyButton( @@ -86,6 +94,23 @@ internal class TokenDetailsNotificationConverter( title = resourceReference(R.string.warning_beacon_chain_retirement_title), subtitle = resourceReference(R.string.warning_beacon_chain_retirement_content), ) + + is HederaWarnings.AssociateWarning -> HederaAssociateWarning( + currency = warning.currency, + fee = null, + feeCurrencySymbol = null, + onAssociateClick = clickIntents::onAssociateClick, + ) + is HederaWarnings.AssociateWarningWithFee -> HederaAssociateWarning( + currency = warning.currency, + fee = BigDecimalFormatter.formatCryptoAmount( + cryptoAmount = warning.fee, + cryptoCurrency = "", + decimals = warning.feeCurrencyDecimals, + ), + feeCurrencySymbol = warning.feeCurrencySymbol, + onAssociateClick = clickIntents::onAssociateClick, + ) } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index db67d88385..1cd62078a1 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -10,7 +10,9 @@ import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.triggeredEvent -import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.CardTypesResolver @@ -35,7 +37,6 @@ import com.tangem.utils.Provider import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import java.lang.IllegalArgumentException @Suppress("TooManyFunctions", "LargeClass") internal class TokenDetailsStateFactory( @@ -187,6 +188,19 @@ internal class TokenDetailsStateFactory( ) } + fun getStateWithErrorDialog(text: TextReference): TokenDetailsState { + return currentStateProvider().copy( + dialogConfig = TokenDetailsDialogConfig( + isShow = true, + onDismissRequest = clickIntents::onDismissDialog, + content = TokenDetailsDialogConfig.DialogContentConfig.ErrorDialogConfig( + text = text, + onConfirmClick = clickIntents::onDismissDialog, + ), + ), + ) + } + fun getRefreshingState(): TokenDetailsState { return refreshStateConverter.convert(true) } @@ -256,6 +270,11 @@ internal class TokenDetailsStateFactory( return state.copy(notifications = notificationConverter.removeRentInfo(state)) } + fun getStateWithRemovedHederaAssociateNotification(): TokenDetailsState { + val state = currentStateProvider() + return state.copy(notifications = notificationConverter.removeHederaAssociateWarning(state)) + } + fun getStateWithExchangeStatusBottomSheet(swapTxState: SwapTransactionsState): TokenDetailsState { return currentStateProvider().copy( bottomSheetConfig = TangemBottomSheetConfig( @@ -379,6 +398,9 @@ internal class TokenDetailsStateFactory( id = R.string.token_button_unavailability_generic_description, ) } + ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference( + id = R.string.warning_receive_blocked_hedera_token_association_required_message, + ) ScenarioUnavailabilityReason.None -> { throw IllegalArgumentException("The unavailability reason must be other than None") } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt index 54c81902ca..751b8dcd92 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt @@ -53,4 +53,6 @@ interface TokenDetailsClickIntents { fun onGenerateExtendedKey() fun onCopyAddress(): TextReference? + + fun onAssociateClick() } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index f40778e2eb..814cc1bcdd 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -17,6 +17,7 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.haptic.HapticManager +import com.tangem.core.ui.extensions.wrappedList import com.tangem.datasource.local.swaptx.SwapTransactionStatusStore import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency @@ -38,6 +39,8 @@ import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.tokens.models.analytics.TokenSwapPromoAnalyticsEvent import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.domain.transaction.error.AssociateAssetError +import com.tangem.domain.transaction.usecase.AssociateAssetUseCase import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase @@ -99,6 +102,7 @@ internal class TokenDetailsViewModel @Inject constructor( private val quotesRepository: QuotesRepository, private val swapTransactionStatusStore: SwapTransactionStatusStore, private val isDemoCardUseCase: IsDemoCardUseCase, + private val associateAssetUseCase: AssociateAssetUseCase, private val reduxStateHolder: ReduxStateHolder, private val analyticsEventsHandler: AnalyticsEventHandler, private val hapticManager: HapticManager, @@ -216,7 +220,7 @@ internal class TokenDetailsViewModel @Inject constructor( .launchIn(viewModelScope) } - private fun updateButtons(currencyStatus: CryptoCurrencyStatus) { + private suspend fun updateButtons(currencyStatus: CryptoCurrencyStatus) { getCryptoCurrencyActionsUseCase( userWallet = userWallet, cryptoCurrencyStatus = currencyStatus, @@ -709,6 +713,36 @@ internal class TokenDetailsViewModel @Inject constructor( return resourceReference(R.string.wallet_notification_address_copied) } + override fun onAssociateClick() { + analyticsEventsHandler.send( + TokenScreenAnalyticsEvent.Associate( + tokenSymbol = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + ), + ) + viewModelScope.launch(dispatchers.io) { + associateAssetUseCase( + userWalletId = userWalletId, + currency = cryptoCurrency, + ).fold( + ifLeft = { e -> + when (e) { + is AssociateAssetError.NotEnoughBalance -> { + uiState = stateFactory.getStateWithErrorDialog( + resourceReference( + id = R.string.warning_hedera_token_association_not_enough_hbar_message, + formatArgs = wrappedList(e.feeCurrency.symbol), + ), + ) + } + is AssociateAssetError.DataError -> Timber.e(e.message) + } + }, + ifRight = { uiState = stateFactory.getStateWithRemovedHederaAssociateNotification() }, + ) + } + } + private fun handleUnavailabilityReason(unavailabilityReason: ScenarioUnavailabilityReason): Boolean { if (unavailabilityReason == ScenarioUnavailabilityReason.None) return false diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index e472dd6eac..53f2f86e93 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -492,6 +492,9 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( id = R.string.token_button_unavailability_generic_description, ) } + ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference( + id = R.string.warning_receive_blocked_hedera_token_association_required_message, + ) ScenarioUnavailabilityReason.None -> { throw IllegalArgumentException("The unavailability reason must be other than None") } diff --git a/settings.gradle.kts b/settings.gradle.kts index b714ecb4b4..eeeaa4c19a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -180,6 +180,7 @@ include(":domain:app-theme:models") include(":domain:balance-hiding") include(":domain:balance-hiding:models") include(":domain:transaction") +include(":domain:transaction:models") include(":domain:analytics") include(":domain:visa") include(":domain:onboarding") From 30ed01cb2fc3ae9c60de447c4ec5bf6c73c6fcc9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 May 2024 14:48:16 +0800 Subject: [PATCH 91/98] Updated on 2026-08-14 --- .../core/ui/components/ButtonsDeprecated.kt | 176 ------------------ .../com/tangem/core/ui/res/ButtonColorType.kt | 24 --- .../com/tangem/core/ui/res/IconColorType.kt | 32 ---- .../com/tangem/core/ui/res/TextColorType.kt | 32 ---- .../referral/ui/NonParticipateBottomBlock.kt | 12 +- .../referral/ui/ParticipateBottomBlock.kt | 10 +- 6 files changed, 13 insertions(+), 273 deletions(-) delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/ButtonColorType.kt delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/IconColorType.kt delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/TextColorType.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt b/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt deleted file mode 100644 index e8612296a5..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt +++ /dev/null @@ -1,176 +0,0 @@ -package com.tangem.core.ui.components - -import android.content.res.Configuration -import androidx.annotation.DrawableRes -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults -import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.R -import com.tangem.core.ui.res.ButtonColorType -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TextColorType -import com.tangem.core.ui.res.buttonColor -import com.tangem.core.ui.res.textColor - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Preview(widthDp = 328, heightDp = 48, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview_PrimaryStartIconButton_Enabled() { - TangemThemePreview { - PrimaryStartIconButton( - text = "Manage tokens", - iconResId = R.drawable.ic_tangem_24, - enabled = true, - onClick = {}, - ) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Preview(widthDp = 328, heightDp = 48, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview_PrimaryStartIconButton_Disabled() { - TangemThemePreview { - PrimaryStartIconButton( - text = "Manage tokens", - iconResId = R.drawable.ic_tangem_24, - enabled = false, - onClick = {}, - ) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Preview(widthDp = 328, heightDp = 48, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview_PrimaryEndIconButton_Enabled() { - TangemThemePreview { - PrimaryEndIconButton( - text = "Manage tokens", - iconResId = R.drawable.ic_tangem_24, - enabled = true, - onClick = {}, - ) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Preview(widthDp = 328, heightDp = 48, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview_PrimaryEndIconButton_Disabled() { - TangemThemePreview { - PrimaryEndIconButton( - text = "Manage tokens", - iconResId = R.drawable.ic_tangem_24, - enabled = false, - onClick = {}, - ) - } -} - -/** - * Primary button with an icon at the beginning of the layout - * - * @param modifier button modifier - * @param text button text - * @param iconResId button icon res id - * @param enabled controls the enabled state of the button - * @param onClick the lambda to be invoked when this button is pressed - * - * @see Figma component - */ -@Deprecated("Use PrimaryButtonIconRight instead") -@Composable -fun PrimaryStartIconButton( - text: String, - @DrawableRes iconResId: Int, - onClick: () -> Unit, - modifier: Modifier = Modifier, - enabled: Boolean = true, -) { - PrimaryButtonRow( - modifier = modifier, - enabled = enabled, - onClick = onClick, - content = { - Icon( - painter = painterResource(id = iconResId), - contentDescription = null, - ) - SpacerW8() - Text(text = text) - }, - ) -} - -/** - * Primary button with an icon at the end of the layout - * - * @param modifier button modifier - * @param text button text - * @param iconResId button icon res id - * @param enabled controls the enabled state of the button - * @param onClick the lambda to be invoked when this button is pressed - * - * @see Figma component - */ -@Deprecated("Use PrimaryButtonIconLeft instead") -@Composable -fun PrimaryEndIconButton( - text: String, - @DrawableRes iconResId: Int, - onClick: () -> Unit, - modifier: Modifier = Modifier, - enabled: Boolean = true, -) { - PrimaryButtonRow( - modifier = modifier, - enabled = enabled, - onClick = onClick, - content = { - Text(text = text) - SpacerW8() - Icon( - painter = painterResource(id = iconResId), - contentDescription = null, - ) - }, - ) -} - -@Composable -private fun PrimaryButtonRow( - enabled: Boolean, - onClick: () -> Unit, - content: @Composable (RowScope.() -> Unit), - modifier: Modifier = Modifier, -) { - Button( - onClick = onClick, - modifier = modifier - .fillMaxWidth() - .height(TangemTheme.dimens.size48), - enabled = enabled, - shape = RoundedCornerShape(TangemTheme.dimens.radius12), - colors = ButtonDefaults.buttonColors( - backgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.PRIMARY), - contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), - disabledBackgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.DISABLED), - disabledContentColor = MaterialTheme.colors.textColor(type = TextColorType.DISABLED), - ), - content = content, - ) -} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColorType.kt b/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColorType.kt deleted file mode 100644 index 2e45dc7577..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColorType.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.core.ui.res - -import androidx.compose.material.Colors -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color -import com.tangem.core.ui.res.TangemColorPalette.Dark5 -import com.tangem.core.ui.res.TangemColorPalette.Dark6 -import com.tangem.core.ui.res.TangemColorPalette.DarkGreen -import com.tangem.core.ui.res.TangemColorPalette.Light1 -import com.tangem.core.ui.res.TangemColorPalette.Light2 -import com.tangem.core.ui.res.TangemColorPalette.MagicMint -import com.tangem.core.ui.res.TangemColorPalette.Meadow - -@Deprecated("Use TangemTheme.colors") -enum class ButtonColorType(val lightColor: Color, val darkColor: Color) { - PRIMARY(lightColor = Dark6, darkColor = Light1), - SECONDARY(lightColor = Light2, darkColor = Dark5), - DISABLED(lightColor = Light2, darkColor = Dark6), - POSITIVE(lightColor = Meadow, darkColor = Meadow), - POSITIVE_DISABLED(lightColor = MagicMint, darkColor = DarkGreen), -} - -@Composable -fun Colors.buttonColor(type: ButtonColorType): Color = if (IS_SYSTEM_IN_DARK_THEME) type.darkColor else type.lightColor \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/IconColorType.kt b/core/ui/src/main/java/com/tangem/core/ui/res/IconColorType.kt deleted file mode 100644 index 42b0e53057..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/res/IconColorType.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.tangem.core.ui.res - -import androidx.compose.material.Colors -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color -import com.tangem.core.ui.res.TangemColorPalette.Amaranth -import com.tangem.core.ui.res.TangemColorPalette.Azure -import com.tangem.core.ui.res.TangemColorPalette.Black -import com.tangem.core.ui.res.TangemColorPalette.Dark1 -import com.tangem.core.ui.res.TangemColorPalette.Dark2 -import com.tangem.core.ui.res.TangemColorPalette.Dark4 -import com.tangem.core.ui.res.TangemColorPalette.Dark6 -import com.tangem.core.ui.res.TangemColorPalette.Light4 -import com.tangem.core.ui.res.TangemColorPalette.Light5 -import com.tangem.core.ui.res.TangemColorPalette.Mustard -import com.tangem.core.ui.res.TangemColorPalette.Tangerine -import com.tangem.core.ui.res.TangemColorPalette.White - -@Deprecated("Use TangemTheme.colors") -enum class IconColorType(val lightColor: Color, val darkColor: Color) { - PRIMARY1(lightColor = Black, darkColor = White), - PRIMARY2(lightColor = White, darkColor = Dark6), - SECONDARY(lightColor = Dark2, darkColor = Dark1), - INFORMATIVE(lightColor = Light5, darkColor = Dark2), - INACTIVE(lightColor = Light4, darkColor = Dark4), - ACCENT(lightColor = Azure, darkColor = Azure), - WARNING(lightColor = Amaranth, darkColor = Amaranth), - ATTENTION(lightColor = Tangerine, darkColor = Mustard), -} - -@Composable -fun Colors.iconColor(type: IconColorType): Color = if (IS_SYSTEM_IN_DARK_THEME) type.darkColor else type.lightColor \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TextColorType.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TextColorType.kt deleted file mode 100644 index b093f4f7af..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TextColorType.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.tangem.core.ui.res - -import androidx.compose.material.Colors -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color -import com.tangem.core.ui.res.TangemColorPalette.Amaranth -import com.tangem.core.ui.res.TangemColorPalette.Dark1 -import com.tangem.core.ui.res.TangemColorPalette.Dark2 -import com.tangem.core.ui.res.TangemColorPalette.Dark3 -import com.tangem.core.ui.res.TangemColorPalette.Dark6 -import com.tangem.core.ui.res.TangemColorPalette.Light4 -import com.tangem.core.ui.res.TangemColorPalette.Light5 -import com.tangem.core.ui.res.TangemColorPalette.Meadow -import com.tangem.core.ui.res.TangemColorPalette.Mustard -import com.tangem.core.ui.res.TangemColorPalette.Tangerine -import com.tangem.core.ui.res.TangemColorPalette.White - -@Deprecated("Use TangemTheme.colors") -enum class TextColorType(val lightColor: Color, val darkColor: Color) { - PRIMARY1(lightColor = Dark6, darkColor = White), - PRIMARY2(lightColor = White, darkColor = Dark6), - SECONDARY(lightColor = Dark2, darkColor = Light5), - TERTIARY(lightColor = Dark1, darkColor = Dark1), - DISABLED(lightColor = Light4, darkColor = Dark3), - ACCENT(lightColor = Meadow, darkColor = Meadow), - WARNING(lightColor = Amaranth, darkColor = Amaranth), - ATTENTION(lightColor = Tangerine, darkColor = Mustard), - CONSTANT_WHITE(lightColor = White, darkColor = White), -} - -@Composable -fun Colors.textColor(type: TextColorType): Color = if (IS_SYSTEM_IN_DARK_THEME) type.darkColor else type.lightColor \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt index d79e4a6f6b..077fb745d0 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt @@ -3,14 +3,15 @@ package com.tangem.feature.referral.ui import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.PrimaryEndIconButton -import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.components.PrimaryButtonIconEnd import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.referral.presentation.R @Composable @@ -20,11 +21,14 @@ internal fun NonParticipateBottomBlock(onAgreementClick: () -> Unit, onParticipa firstPartResId = R.string.referral_tos_not_enroled_prefix, onClick = onAgreementClick, ) - PrimaryEndIconButton( - modifier = Modifier.padding(all = TangemTheme.dimens.spacing16), + + PrimaryButtonIconEnd( text = stringResource(id = R.string.referral_button_participate), iconResId = R.drawable.ic_tangem_24, onClick = onParticipateClick, + modifier = Modifier + .fillMaxWidth() + .padding(all = TangemTheme.dimens.spacing16), ) } } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index 7890ba08a7..39257e4570 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.core.content.ContextCompat.startActivity -import com.tangem.core.ui.components.PrimaryStartIconButton +import com.tangem.core.ui.components.PrimaryButtonIconStart import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.referral.domain.models.ExpectedAward @@ -281,8 +281,7 @@ private fun AdditionalButtons( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), ) { - PrimaryStartIconButton( - modifier = Modifier.weight(1f), + PrimaryButtonIconStart( text = stringResource(id = R.string.common_copy), iconResId = R.drawable.ic_copy_24, onClick = { @@ -297,11 +296,11 @@ private fun AdditionalButtons( ) } }, + modifier = Modifier.weight(1f), ) val context = LocalContext.current - PrimaryStartIconButton( - modifier = Modifier.weight(1f), + PrimaryButtonIconStart( text = stringResource(id = R.string.common_share), iconResId = R.drawable.ic_share_24, onClick = { @@ -309,6 +308,7 @@ private fun AdditionalButtons( hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) context.shareText(context.getString(R.string.referral_share_link, shareLink)) }, + modifier = Modifier.weight(1f), ) } } From 8dffffac3b2d1897e3440fe2882ce7cc13418179 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 May 2024 15:09:42 +0500 Subject: [PATCH 92/98] Updated on 2026-08-14 --- .../ui/components/inputrow/InputRowRecipient.kt | 4 +++- .../impl/presentation/state/SendStateFactory.kt | 11 +++++++---- .../presentation/state/fields/SendTextField.kt | 2 ++ .../previewdata/RecipientStatePreviewData.kt | 1 + .../state/previewdata/SendClickIntentsStub.kt | 5 +++-- .../SendRecipientAddressFieldConverter.kt | 1 + .../recipient/SendRecipientMemoFieldConverter.kt | 1 + .../ui/recipient/SendRecipientContent.kt | 4 +++- .../ui/recipient/TextFieldWithPaste.kt | 2 ++ .../presentation/viewmodel/SendClickIntents.kt | 2 +- .../impl/presentation/viewmodel/SendViewModel.kt | 15 ++++++--------- 11 files changed, 30 insertions(+), 18 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt index de4873ae76..fad31aa600 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt @@ -23,8 +23,8 @@ import com.tangem.core.ui.components.inputrow.inner.DividerContainer import com.tangem.core.ui.components.inputrow.inner.PasteButton import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import kotlinx.coroutines.delay /** @@ -56,6 +56,7 @@ fun InputRowRecipient( isError: Boolean = false, showDivider: Boolean = false, isLoading: Boolean = false, + isValuePasted: Boolean = false, ) { val (titleText, color) = if (isError && error != null) { error to TangemTheme.colors.text.warning @@ -92,6 +93,7 @@ fun InputRowRecipient( placeholder = placeholder, onValueChange = onValueChange, singleLine = singleLine, + isValuePasted = isValuePasted, modifier = Modifier .padding(start = TangemTheme.dimens.spacing12) .weight(1f) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index f2ea95f678..7900acf6a3 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -151,14 +151,14 @@ internal class SendStateFactory( ) } - fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false): SendUiState { + fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false, isValuePasted: Boolean): SendUiState { val state = currentStateProvider() val isEditState = stateRouterProvider().isEditState val recipientState = state.getRecipientState(isEditState) ?: return state return state.copyWrapped( isEditState = isEditState, recipientState = recipientState.copy( - addressTextField = recipientState.addressTextField.copy(value = value), + addressTextField = recipientState.addressTextField.copy(value = value, isValuePasted = isValuePasted), memoTextField = recipientState.memoTextField?.copy(isEnabled = !isXAddress), ), ) @@ -207,14 +207,17 @@ internal class SendStateFactory( ) } - fun getOnRecipientMemoValueChange(value: String): SendUiState { + fun getOnRecipientMemoValueChange(value: String, isValuePasted: Boolean): SendUiState { val state = currentStateProvider() val isEditState = stateRouterProvider().isEditState val recipientState = state.getRecipientState(isEditState) ?: return state return state.copyWrapped( isEditState = isEditState, recipientState = recipientState.copy( - memoTextField = recipientState.memoTextField?.copy(value = value), + memoTextField = recipientState.memoTextField?.copy( + value = value, + isValuePasted = isValuePasted, + ), ), ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt index 6cd4f8c31d..4f5ce27038 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt @@ -42,6 +42,7 @@ internal sealed class SendTextField { val label: TextReference, val isError: Boolean = false, val error: TextReference? = null, + val isValuePasted: Boolean, ) : SendTextField() data class RecipientMemo( @@ -54,6 +55,7 @@ internal sealed class SendTextField { val error: TextReference? = null, val disabledText: TextReference, val isEnabled: Boolean, + val isValuePasted: Boolean, ) : SendTextField() data class CustomFee( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/RecipientStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/RecipientStatePreviewData.kt index 65ab1d68bf..98c97835fd 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/RecipientStatePreviewData.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/RecipientStatePreviewData.kt @@ -30,6 +30,7 @@ internal object RecipientStatePreviewData { label = stringReference("Recipient"), isError = false, error = null, + isValuePasted = false, ), memoTextField = null, recent = persistentListOf( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index 0fedb031f2..3df4e66efc 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -36,7 +36,7 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) {} - override fun onRecipientMemoValueChange(value: String) {} + override fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean) {} override fun feeReload() {} @@ -64,7 +64,8 @@ internal object SendClickIntentsStub : SendClickIntents { reduceAmountBy: BigDecimal?, reduceAmountTo: BigDecimal?, clazz: Class, - ) {} + ) { + } override fun onNotificationCancel(clazz: Class) {} } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt index ab822de809..2365bc31e2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientAddressFieldConverter.kt @@ -24,6 +24,7 @@ internal class SendRecipientAddressFieldConverter( error = resourceReference(R.string.send_recipient_address_error), placeholder = resourceReference(R.string.send_enter_address_field), label = resourceReference(R.string.send_recipient), + isValuePasted = false, ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt index a539d72859..1525d0d6ab 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt @@ -58,6 +58,7 @@ internal class SendRecipientMemoFieldConverter( error = resourceReference(R.string.send_memo_destination_tag_error), disabledText = resourceReference(R.string.send_additional_field_already_included), isEnabled = true, + isValuePasted = false, ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index 66fca69ea6..78f2b8a1c0 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -69,7 +69,7 @@ internal fun SendRecipientContent( ) memoField( memoField = memoField, - onMemoChange = clickIntents::onRecipientMemoValueChange, + onMemoChange = { clickIntents.onRecipientMemoValueChange(it, true) }, ) listHeaderItem( titleRes = R.string.send_recipient_wallets_title, @@ -116,6 +116,7 @@ private fun LazyListScope.addressItem( isError = isError, isLoading = isValidating, error = address.error, + isValuePasted = address.isValuePasted, modifier = Modifier .background( color = TangemTheme.colors.background.action, @@ -141,6 +142,7 @@ private fun LazyListScope.memoField(memoField: SendTextField.RecipientMemo?, onM isError = memoField.isError, error = memoField.error, isReadOnly = !memoField.isEnabled, + isValuePasted = memoField.isValuePasted, ) } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt index c59617aac0..b6afef71c7 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt @@ -30,6 +30,7 @@ internal fun TextFieldWithPaste( error: TextReference? = null, isError: Boolean = false, isReadOnly: Boolean = false, + isValuePasted: Boolean = false, ) { val (title, color) = when { isError && error != null -> error to TangemTheme.colors.text.warning @@ -63,6 +64,7 @@ internal fun TextFieldWithPaste( placeholderColor = placeholderColor, onValueChange = onValueChange, readOnly = isReadOnly, + isValuePasted = isValuePasted, modifier = Modifier .fillMaxWidth() .padding(top = TangemTheme.dimens.spacing6), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index 32ba81ce73..0901e3c3ce 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -39,7 +39,7 @@ internal interface SendClickIntents { // region Recipient fun onRecipientAddressValueChange(value: String, type: EnterAddressSource? = null) - fun onRecipientMemoValueChange(value: String) + fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean = false) // endregion // region Fee diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 276c2bfcd3..40737d0156 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -52,10 +52,7 @@ import com.tangem.features.send.impl.presentation.state.confirm.SendNotification import com.tangem.features.send.impl.presentation.state.fee.* import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.DelayedWork -import com.tangem.utils.coroutines.JobHolder -import com.tangem.utils.coroutines.saveIn +import com.tangem.utils.coroutines.* import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* @@ -586,7 +583,7 @@ internal class SendViewModel @Inject constructor( ) } - // endregion +// endregion // region amount state clicks override fun onCurrencyChangeClick(isFiat: Boolean) { @@ -612,7 +609,7 @@ internal class SendViewModel @Inject constructor( override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) { viewModelScope.launch { if (!checkIfXrpAddressValue(value)) { - uiState = stateFactory.onRecipientAddressValueChange(value) + uiState = stateFactory.onRecipientAddressValueChange(value, isValuePasted = type != null) uiState = stateFactory.getOnRecipientAddressValidationStarted() val isValidAddress = validateAddress(value) uiState = stateFactory.getOnRecipientAddressValidState(value, isValidAddress) @@ -622,10 +619,10 @@ internal class SendViewModel @Inject constructor( }.saveIn(addressValidationJobHolder) } - override fun onRecipientMemoValueChange(value: String) { + override fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean) { viewModelScope.launch { if (!checkIfXrpAddressValue(value)) { - uiState = stateFactory.getOnRecipientMemoValueChange(value) + uiState = stateFactory.getOnRecipientMemoValueChange(value, isValuePasted) uiState = stateFactory.getOnRecipientAddressValidationStarted() val isValidAddress = validateAddress(uiState.recipientState?.addressTextField?.value.orEmpty()) uiState = stateFactory.getOnRecipientMemoValidState(value, isValidAddress) @@ -647,7 +644,7 @@ internal class SendViewModel @Inject constructor( private suspend fun checkIfXrpAddressValue(value: String): Boolean { return BlockchainUtils.decodeRippleXAddress(value, cryptoCurrency.network.id.value)?.let { decodedAddress -> - uiState = stateFactory.onRecipientAddressValueChange(value, isXAddress = true) + uiState = stateFactory.onRecipientAddressValueChange(value, isXAddress = true, isValuePasted = true) uiState = stateFactory.getOnXAddressMemoState() val isValidAddress = validateAddress(decodedAddress.address) uiState = stateFactory.getOnRecipientAddressValidState(decodedAddress.address, isValidAddress) From 0a271314bb5dc22441cd8dacb92503455c5a42d1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 May 2024 17:42:09 +0500 Subject: [PATCH 93/98] Updated on 2026-08-14 --- .../presentation/state/SendStateFactory.kt | 175 +---------------- .../state/recipient/RecipientSendFactory.kt | 182 ++++++++++++++++++ .../presentation/viewmodel/SendViewModel.kt | 44 +++-- 3 files changed, 211 insertions(+), 190 deletions(-) create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/RecipientSendFactory.kt diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index 7900acf6a3..3d038ef6a6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -1,18 +1,11 @@ package com.tangem.features.send.impl.presentation.state -import arrow.core.getOrElse import com.tangem.blockchain.common.TransactionData import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.event.consumedEvent -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter import com.tangem.features.send.impl.presentation.state.common.SendSyncEditConverter import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter @@ -20,18 +13,14 @@ import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter import com.tangem.features.send.impl.presentation.state.fee.checkFeeCoverage import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter -import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientHistoryListConverter import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter -import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientWalletListConverter import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toPersistentList -import timber.log.Timber import java.math.BigDecimal -@Suppress("LongParameterList", "LargeClass") +@Suppress("LongParameterList") internal class SendStateFactory( private val clickIntents: SendClickIntents, private val stateRouterProvider: Provider, @@ -41,7 +30,6 @@ internal class SendStateFactory( private val cryptoCurrencyStatusProvider: Provider, private val feeCryptoCurrencyStatusProvider: Provider, private val isTapHelpPreviewEnabledProvider: Provider, - private val validateWalletMemoUseCase: ValidateWalletMemoUseCase, ) { private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) @@ -79,14 +67,6 @@ internal class SendStateFactory( isTapHelpPreviewEnabledProvider = isTapHelpPreviewEnabledProvider, ) } - private val recipientWalletListStateConverter by lazy(LazyThreadSafetyMode.NONE) { - SendRecipientWalletListConverter() - } - private val recipientHistoryListStateConverter by lazy(LazyThreadSafetyMode.NONE) { - SendRecipientHistoryListConverter( - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - ) - } private val sendSyncEditConverter by lazy(LazyThreadSafetyMode.NONE) { SendSyncEditConverter(currentStateProvider = currentStateProvider) } @@ -132,159 +112,6 @@ internal class SendStateFactory( } //endregion - //region recipient - fun onLoadedWalletsList(wallets: List): SendUiState { - val state = currentStateProvider() - return state.copy( - recipientState = state.recipientState?.copy( - wallets = recipientWalletListStateConverter.convert(wallets), - ), - ) - } - - fun onLoadedHistoryList(txHistory: List): SendUiState { - val state = currentStateProvider() - return state.copy( - recipientState = state.recipientState?.copy( - recent = recipientHistoryListStateConverter.convert(txHistory), - ), - ) - } - - fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false, isValuePasted: Boolean): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - addressTextField = recipientState.addressTextField.copy(value = value, isValuePasted = isValuePasted), - memoTextField = recipientState.memoTextField?.copy(isEnabled = !isXAddress), - ), - ) - } - - fun getOnRecipientAddressValidState(value: String, isValidAddress: Boolean): SendUiState { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - - val isValidMemo = validateWalletMemoUseCase( - memo = recipientState.memoTextField?.value.orEmpty(), - network = cryptoCurrencyStatus.currency.network, - ).getOrElse { - Timber.e("Failed to validateWalletMemoUseCase: $it") - false - } - val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses - ?.any { it.value == value } ?: true - - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet, - isValidating = false, - addressTextField = recipientState.addressTextField.copy( - error = when { - !isValidAddress -> resourceReference(R.string.send_recipient_address_error) - isAddressInWallet -> resourceReference(R.string.send_error_address_same_as_wallet) - else -> null - }, - isError = value.isNotEmpty() && !isValidAddress || isAddressInWallet, - ), - ), - ) - } - - fun getOnRecipientAddressValidationStarted(): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy(isValidating = true), - ) - } - - fun getOnRecipientMemoValueChange(value: String, isValuePasted: Boolean): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - memoTextField = recipientState.memoTextField?.copy( - value = value, - isValuePasted = isValuePasted, - ), - ), - ) - } - - fun getOnRecipientMemoValidState(value: String, isValidAddress: Boolean): SendUiState { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - - val isValidMemo = validateWalletMemoUseCase( - memo = value, - network = cryptoCurrencyStatus.currency.network, - ).getOrElse { - Timber.e("Failed to validateWalletMemoUseCase: $it") - false - } - val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses - ?.any { it.value == value } ?: true - - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet, - isValidating = false, - memoTextField = recipientState.memoTextField?.copy( - isError = value.isNotEmpty() && !isValidMemo, - isEnabled = true, - ), - ), - ) - } - - fun getOnXAddressMemoState(): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - memoTextField = recipientState.memoTextField?.copy( - value = "", - isEnabled = false, - ), - ), - ) - } - - fun getHiddenRecentListState(isAddressInWallet: Boolean, isValidAddress: Boolean): SendUiState { - val state = currentStateProvider() - val isEditState = stateRouterProvider().isEditState - val recipientState = state.getRecipientState(isEditState) ?: return state - val isNotValid = isAddressInWallet || !isValidAddress - return state.copyWrapped( - isEditState = isEditState, - recipientState = recipientState.copy( - recent = recipientState.recent.map { recent -> - recent.copy(isVisible = isNotValid && (recent.isLoading || recent.title != TextReference.EMPTY)) - }.toPersistentList(), - wallets = recipientState.wallets.map { wallet -> - wallet.copy(isVisible = isNotValid && (wallet.isLoading || wallet.title != TextReference.EMPTY)) - }.toPersistentList(), - ), - ) - } - //endregion - //region send fun getIsAmountSubtractedState(isAmountSubtractAvailable: Boolean): SendUiState { val state = currentStateProvider() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/RecipientSendFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/RecipientSendFactory.kt new file mode 100644 index 0000000000..5fcb985783 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/RecipientSendFactory.kt @@ -0,0 +1,182 @@ +package com.tangem.features.send.impl.presentation.state.recipient + +import arrow.core.getOrElse +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.txhistory.models.TxHistoryItem +import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase +import com.tangem.features.send.impl.R +import com.tangem.features.send.impl.presentation.domain.AvailableWallet +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.StateRouter +import com.tangem.utils.Provider +import kotlinx.collections.immutable.toPersistentList +import timber.log.Timber + +internal class RecipientSendFactory( + private val stateRouterProvider: Provider, + private val currentStateProvider: Provider, + private val cryptoCurrencyStatusProvider: Provider, + private val validateWalletMemoUseCase: ValidateWalletMemoUseCase, +) { + private val recipientWalletListStateConverter by lazy(LazyThreadSafetyMode.NONE) { + SendRecipientWalletListConverter() + } + private val recipientHistoryListStateConverter by lazy(LazyThreadSafetyMode.NONE) { + SendRecipientHistoryListConverter( + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + ) + } + + fun onLoadedWalletsList(wallets: List): SendUiState { + val state = currentStateProvider() + return state.copy( + recipientState = state.recipientState?.copy( + wallets = recipientWalletListStateConverter.convert(wallets), + ), + ) + } + + fun onLoadedHistoryList(txHistory: List): SendUiState { + val state = currentStateProvider() + return state.copy( + recipientState = state.recipientState?.copy( + recent = recipientHistoryListStateConverter.convert(txHistory), + ), + ) + } + + fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false, isValuePasted: Boolean): SendUiState { + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy( + addressTextField = recipientState.addressTextField.copy(value = value, isValuePasted = isValuePasted), + memoTextField = recipientState.memoTextField?.copy(isEnabled = !isXAddress), + ), + ) + } + + fun getOnRecipientAddressValidState(value: String, isValidAddress: Boolean): SendUiState { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + + val isValidMemo = validateWalletMemoUseCase( + memo = recipientState.memoTextField?.value.orEmpty(), + network = cryptoCurrencyStatus.currency.network, + ).getOrElse { + Timber.e("Failed to validateWalletMemoUseCase: $it") + false + } + val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses + ?.any { it.value == value } ?: true + + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy( + isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet, + isValidating = false, + addressTextField = recipientState.addressTextField.copy( + error = when { + !isValidAddress -> resourceReference(R.string.send_recipient_address_error) + isAddressInWallet -> resourceReference(R.string.send_error_address_same_as_wallet) + else -> null + }, + isError = value.isNotEmpty() && !isValidAddress || isAddressInWallet, + ), + ), + ) + } + + fun getOnRecipientAddressValidationStarted(): SendUiState { + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy(isValidating = true), + ) + } + + fun getOnRecipientMemoValueChange(value: String, isValuePasted: Boolean): SendUiState { + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy( + memoTextField = recipientState.memoTextField?.copy( + value = value, + isValuePasted = isValuePasted, + ), + ), + ) + } + + fun getOnRecipientMemoValidState(value: String, isValidAddress: Boolean): SendUiState { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + + val isValidMemo = validateWalletMemoUseCase( + memo = value, + network = cryptoCurrencyStatus.currency.network, + ).getOrElse { + Timber.e("Failed to validateWalletMemoUseCase: $it") + false + } + val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses + ?.any { it.value == value } ?: true + + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy( + isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet, + isValidating = false, + memoTextField = recipientState.memoTextField?.copy( + isError = value.isNotEmpty() && !isValidMemo, + isEnabled = true, + ), + ), + ) + } + + fun getOnXAddressMemoState(): SendUiState { + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy( + memoTextField = recipientState.memoTextField?.copy( + value = "", + isEnabled = false, + ), + ), + ) + } + + fun getHiddenRecentListState(isAddressInWallet: Boolean, isValidAddress: Boolean): SendUiState { + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val recipientState = state.getRecipientState(isEditState) ?: return state + val isNotValid = isAddressInWallet || !isValidAddress + return state.copyWrapped( + isEditState = isEditState, + recipientState = recipientState.copy( + recent = recipientState.recent.map { recent -> + recent.copy(isVisible = isNotValid && (recent.isLoading || recent.title != TextReference.EMPTY)) + }.toPersistentList(), + wallets = recipientState.wallets.map { wallet -> + wallet.copy(isVisible = isNotValid && (wallet.isLoading || wallet.title != TextReference.EMPTY)) + }.toPersistentList(), + ), + ) + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 40737d0156..5b9d5f336f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -50,9 +50,13 @@ import com.tangem.features.send.impl.presentation.state.* import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactory import com.tangem.features.send.impl.presentation.state.confirm.SendNotificationFactory import com.tangem.features.send.impl.presentation.state.fee.* +import com.tangem.features.send.impl.presentation.state.recipient.RecipientSendFactory import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider -import com.tangem.utils.coroutines.* +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.DelayedWork +import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.saveIn import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* @@ -128,10 +132,16 @@ internal class SendViewModel @Inject constructor( appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, feeCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus }, - validateWalletMemoUseCase = validateWalletMemoUseCase, isTapHelpPreviewEnabledProvider = Provider { isTapHelpPreviewEnabled }, ) + private val recipientStateFactory = RecipientSendFactory( + stateRouterProvider = Provider { stateRouter }, + currentStateProvider = Provider { uiState }, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + validateWalletMemoUseCase = validateWalletMemoUseCase, + ) + private val amountStateFactory = AmountStateFactory( stateRouterProvider = Provider { stateRouter }, currentStateProvider = Provider { uiState }, @@ -407,9 +417,9 @@ internal class SendViewModel @Inject constructor( .orEmpty() }.onSuccess { result -> userWallets = result - uiState = stateFactory.onLoadedWalletsList(wallets = userWallets) + uiState = recipientStateFactory.onLoadedWalletsList(wallets = userWallets) }.onFailure { - uiState = stateFactory.onLoadedWalletsList(wallets = emptyList()) + uiState = recipientStateFactory.onLoadedWalletsList(wallets = emptyList()) } } } @@ -449,7 +459,7 @@ internal class SendViewModel @Inject constructor( userWalletId = userWalletId, currency = cryptoCurrency, ).getOrElse { emptyList() } - uiState = stateFactory.onLoadedHistoryList(txHistory = txHistoryList) + uiState = recipientStateFactory.onLoadedHistoryList(txHistory = txHistoryList) } private fun onStateActive() { @@ -609,10 +619,10 @@ internal class SendViewModel @Inject constructor( override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) { viewModelScope.launch { if (!checkIfXrpAddressValue(value)) { - uiState = stateFactory.onRecipientAddressValueChange(value, isValuePasted = type != null) - uiState = stateFactory.getOnRecipientAddressValidationStarted() + uiState = recipientStateFactory.onRecipientAddressValueChange(value, isValuePasted = type != null) + uiState = recipientStateFactory.getOnRecipientAddressValidationStarted() val isValidAddress = validateAddress(value) - uiState = stateFactory.getOnRecipientAddressValidState(value, isValidAddress) + uiState = recipientStateFactory.getOnRecipientAddressValidState(value, isValidAddress) type?.let { analyticsEventHandler.send(SendAnalyticEvents.AddressEntered(it, isValidAddress)) } autoNextFromRecipient(type, isValidAddress) } @@ -622,10 +632,11 @@ internal class SendViewModel @Inject constructor( override fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean) { viewModelScope.launch { if (!checkIfXrpAddressValue(value)) { - uiState = stateFactory.getOnRecipientMemoValueChange(value, isValuePasted) - uiState = stateFactory.getOnRecipientAddressValidationStarted() - val isValidAddress = validateAddress(uiState.recipientState?.addressTextField?.value.orEmpty()) - uiState = stateFactory.getOnRecipientMemoValidState(value, isValidAddress) + uiState = recipientStateFactory.getOnRecipientMemoValueChange(value, isValuePasted) + uiState = recipientStateFactory.getOnRecipientAddressValidationStarted() + val recipientState = uiState.getRecipientState(stateRouter.isEditState) + val isValidAddress = validateAddress(recipientState?.addressTextField?.value.orEmpty()) + uiState = recipientStateFactory.getOnRecipientMemoValidState(value, isValidAddress) } }.saveIn(memoValidationJobHolder) } @@ -644,16 +655,17 @@ internal class SendViewModel @Inject constructor( private suspend fun checkIfXrpAddressValue(value: String): Boolean { return BlockchainUtils.decodeRippleXAddress(value, cryptoCurrency.network.id.value)?.let { decodedAddress -> - uiState = stateFactory.onRecipientAddressValueChange(value, isXAddress = true, isValuePasted = true) - uiState = stateFactory.getOnXAddressMemoState() + uiState = + recipientStateFactory.onRecipientAddressValueChange(value, isXAddress = true, isValuePasted = true) + uiState = recipientStateFactory.getOnXAddressMemoState() val isValidAddress = validateAddress(decodedAddress.address) - uiState = stateFactory.getOnRecipientAddressValidState(decodedAddress.address, isValidAddress) + uiState = recipientStateFactory.getOnRecipientAddressValidState(decodedAddress.address, isValidAddress) true } ?: false } private fun onEnteredValidAddress(isValidAddress: Boolean, isAddressInWallet: Boolean) { - uiState = stateFactory.getHiddenRecentListState( + uiState = recipientStateFactory.getHiddenRecentListState( isAddressInWallet = isAddressInWallet, isValidAddress = isValidAddress, ) From 99c984bd3888d574fdeaadbcb03d033c2870c980 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 May 2024 17:02:50 +0800 Subject: [PATCH 94/98] Updated on 2026-08-14 --- .../tap/common/feedback/FeedbackManager.kt | 52 ++++++++++++++---- .../tap/common/redux/global/GlobalAction.kt | 2 - .../common/redux/global/GlobalMiddleware.kt | 4 -- .../com/tangem/tap/domain/TapWalletManager.kt | 9 +++- .../tangem/tap/features/main/MainViewModel.kt | 22 ++++---- .../redux/middlewares/RequestFeeMiddleware.kt | 54 +++++++++++++++---- .../send/redux/middlewares/SendMiddleware.kt | 34 +++++++++--- 7 files changed, 133 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt index 2f7a4ac1cc..98aa013e84 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt @@ -1,15 +1,22 @@ package com.tangem.tap.common.feedback import android.content.Context +import com.tangem.core.navigation.email.EmailSender import com.tangem.datasource.config.models.ChatConfig import com.tangem.domain.common.TapWorkarounds import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.feedback.GetFeedbackEmailUseCase +import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.tap.common.chat.ChatManager +import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.sendEmail import com.tangem.tap.common.log.TangemLogCollector import com.tangem.tap.foregroundActivityObserver +import com.tangem.tap.mainScope +import com.tangem.tap.proxy.redux.DaggerGraphState +import com.tangem.tap.store import com.tangem.tap.withForegroundActivity +import kotlinx.coroutines.launch import timber.log.Timber import java.io.File import java.io.FileWriter @@ -22,23 +29,46 @@ class FeedbackManager( val infoHolder: AdditionalFeedbackInfo, private val logCollector: TangemLogCollector, private val chatManager: ChatManager, - @Suppress("UnusedPrivateMember") private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles, - @Suppress("UnusedPrivateMember") private val getFeedbackEmailUseCase: GetFeedbackEmailUseCase, + private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles, + private val getFeedbackEmailUseCase: GetFeedbackEmailUseCase, ) { private var sessionFeedbackFile: File? = null private var sessionLogsFile: File? = null fun sendEmail(feedbackData: FeedbackData, onFail: ((Exception) -> Unit)? = null) { - feedbackData.prepare(infoHolder) - foregroundActivityObserver.withForegroundActivity { activity -> - activity.sendEmail( - email = getSupportEmail(), - subject = activity.getString(feedbackData.subjectResId), - message = feedbackData.joinTogether(activity, infoHolder), - file = getLogFile(activity), - onFail = onFail, - ) + if (feedbackManagerFeatureToggles.isLocalLogsEnabled) { + mainScope.launch { + val email = getFeedbackEmailUseCase( + when (feedbackData) { + is FeedbackEmail -> FeedbackEmailType.DirectUserRequest + is RateCanBeBetterEmail -> FeedbackEmailType.RateCanBeBetter + is ScanFailsEmail -> FeedbackEmailType.ScanningProblem + is SendTransactionFailedEmail -> FeedbackEmailType.TransactionSendingProblem + else -> FeedbackEmailType.DirectUserRequest + }, + ) + + store.inject(DaggerGraphState::emailSender).send( + email = EmailSender.Email( + address = email.address, + subject = email.subject, + message = email.message, + attachment = email.file, + ), + ) + } + } else { + feedbackData.prepare(infoHolder) + foregroundActivityObserver.withForegroundActivity { activity -> + activity.sendEmail( + email = getSupportEmail(), + subject = activity.getString(feedbackData.subjectResId), + message = feedbackData.joinTogether(activity, infoHolder), + file = getLogFile(activity), + onFail = onFail, + ) + } } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 4fb2375cf9..8439279a0a 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -1,6 +1,5 @@ package com.tangem.tap.common.redux.global -import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.navigation.StateDialog @@ -84,7 +83,6 @@ sealed class GlobalAction : Action { data class SendEmail(val feedbackData: FeedbackData) : GlobalAction() data class OpenChat(val feedbackData: FeedbackData, val chatConfig: ChatConfig? = null) : GlobalAction() - data class UpdateFeedbackInfo(val walletManagers: List) : GlobalAction() object ExchangeManager : GlobalAction() { object Init : GlobalAction() { diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index b50d350a28..fc584f6b8e 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -91,10 +91,6 @@ private fun handleAction(action: Action, appState: () -> AppState?) { } feedbackManager.openChat(chatConfig, action.feedbackData) } - is GlobalAction.UpdateFeedbackInfo -> { - store.state.globalState.feedbackManager?.infoHolder - ?.setWalletsInfo(action.walletManagers) - } is GlobalAction.ExchangeManager.Init -> { val appStateSafe = appState() ?: return val config = appStateSafe.globalState.configManager?.config ?: return diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 14f5f5e725..256c8f1bf9 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -10,11 +10,13 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.models.UserWallet import com.tangem.operations.attestation.Attestation +import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.disclaimer.createDisclaimer import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider @@ -51,7 +53,12 @@ class TapWalletManager( val attestationFailed = card.attestation.status == Attestation.Status.Failed tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) - store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse) + + val featureToggles = store.inject(DaggerGraphState::feedbackManagerFeatureToggles) + if (!featureToggles.isLocalLogsEnabled) { + store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse) + } + updateConfigManager(scanResponse) withMainContext { // Order is important diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 6840975e44..53a3565731 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -13,11 +13,12 @@ import com.tangem.domain.balancehiding.BalanceHidingSettings import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.ListenToFlipsUseCase import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase +import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase -import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.tap.common.extensions.setContext import com.tangem.tap.features.main.model.MainScreenState import com.tangem.tap.store @@ -41,6 +42,7 @@ internal class MainViewModel @Inject constructor( private val userWalletsListManager: UserWalletsListManager, private val walletManagersFacade: WalletManagersFacade, private val sendFeatureToggles: SendFeatureToggles, + private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles, private val dispatchers: CoroutineDispatcherProvider, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel(), MainIntents { @@ -90,15 +92,17 @@ internal class MainViewModel @Inject constructor( Analytics.setContext(userWallet.scanResponse) Analytics.send(Basic.WalletOpened()) - store.state.globalState.feedbackManager?.infoHolder?.let { infoHolder -> - infoHolder.setCardInfo(userWallet.scanResponse) + if (!feedbackManagerFeatureToggles.isLocalLogsEnabled) { + store.state.globalState.feedbackManager?.infoHolder?.let { infoHolder -> + infoHolder.setCardInfo(userWallet.scanResponse) - walletManagersFacade - .getAll(userWallet.walletId) - .distinctUntilChanged() - .onEach(infoHolder::setWalletsInfo) - .catch { Timber.e(it) } - .launchIn(viewModelScope) + walletManagersFacade + .getAll(userWallet.walletId) + .distinctUntilChanged() + .onEach(infoHolder::setWalletsInfo) + .catch { Timber.e(it) } + .launchIn(viewModelScope) + } } } .flowOn(dispatchers.io) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt index cfdcf74a19..e3096c6a4f 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt @@ -1,19 +1,21 @@ package com.tangem.tap.features.send.redux.middlewares -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.TransactionSender +import com.tangem.blockchain.common.* import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.common.extensions.isZero -import com.tangem.tap.common.redux.AppState import com.tangem.domain.demo.DemoTransactionSender +import com.tangem.domain.feedback.models.BlockchainErrorInfo +import com.tangem.tap.common.extensions.inject +import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.send.redux.AmountActionUi import com.tangem.tap.features.send.redux.FeeAction import com.tangem.tap.features.send.redux.ReceiptAction import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.features.send.redux.states.SendState +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.Dispatchers @@ -27,6 +29,7 @@ import java.math.BigDecimal */ class RequestFeeMiddleware { + @Suppress("CyclomaticComplexMethod") fun handle(appState: AppState?, dispatch: DispatchFunction) { val sendState = appState?.sendState ?: return val walletManager = sendState.walletManager ?: return @@ -45,7 +48,7 @@ class RequestFeeMiddleware { val txSender = if (scanResponse.isDemoCard()) { DemoTransactionSender(walletManager) } else { - walletManager as TransactionSender + walletManager } scope.launch { val feeResult = txSender.getFee(destinationAmount, destinationAddress) @@ -73,12 +76,17 @@ class RequestFeeMiddleware { dispatch(FeeAction.FeeCalculation.ClearResult) dispatch(FeeAction.ChangeLayoutVisibility(main = false)) - store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError( - walletManager = walletManager, - amountToSend = destinationAmount, - feeAmount = null, - destinationAddress = destinationAddress, - ) + val featureToggles = store.inject(DaggerGraphState::feedbackManagerFeatureToggles) + if (featureToggles.isLocalLogsEnabled) { + saveBlockchainError(feeResult, destinationAddress, destinationAmount, walletManager) + } else { + store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError( + walletManager = walletManager, + amountToSend = destinationAmount, + feeAmount = null, + destinationAddress = destinationAddress, + ) + } val blockchainSdkError = feeResult.error as? BlockchainSdkError ?: return@withContext dispatch( @@ -93,4 +101,28 @@ class RequestFeeMiddleware { } } } + + private fun saveBlockchainError( + feeResult: Result.Failure, + destinationAddress: String, + destinationAmount: Amount, + walletManager: WalletManager, + ) { + store.inject(DaggerGraphState::saveBlockchainErrorUseCase).invoke( + error = BlockchainErrorInfo( + errorMessage = (feeResult.error as? BlockchainSdkError)?.customMessage + ?: "It isn't BlockchainSdkError", + blockchainId = walletManager.wallet.blockchain.id, + derivationPath = walletManager.wallet.publicKey.derivationPath?.rawPath ?: "", + destinationAddress = destinationAddress, + tokenSymbol = if (destinationAmount.type is AmountType.Token) { + destinationAmount.currencySymbol + } else { + "" + }, + amount = destinationAmount.value?.stripZeroPlainString() ?: "0", + fee = null, + ), + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 86a40c5ccf..9067bffe39 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -21,6 +21,7 @@ import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.demo.DemoTransactionSender +import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.tap.common.analytics.events.Token @@ -274,6 +275,7 @@ private fun sendTransaction( } is SimpleResult.Failure -> { updateFeedbackManagerInfo( + sendResult = sendResult.error, walletManager = walletManager, amountToSend = amountToSend, feeAmount = fee.amount, @@ -364,13 +366,33 @@ private fun updateFeedbackManagerInfo( amountToSend: Amount, feeAmount: Amount, destinationAddress: String, + sendResult: BlockchainError, ) { - store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError( - walletManager = walletManager, - amountToSend = amountToSend, - feeAmount = feeAmount, - destinationAddress = destinationAddress, - ) + val featureToggles = store.inject(DaggerGraphState::feedbackManagerFeatureToggles) + if (featureToggles.isLocalLogsEnabled) { + store.inject(DaggerGraphState::saveBlockchainErrorUseCase).invoke( + error = BlockchainErrorInfo( + errorMessage = (sendResult as? BlockchainSdkError)?.customMessage ?: "It isn't BlockchainSdkError", + blockchainId = walletManager.wallet.blockchain.id, + derivationPath = walletManager.wallet.publicKey.derivationPath?.rawPath ?: "", + destinationAddress = destinationAddress, + tokenSymbol = if (amountToSend.type is AmountType.Token) { + amountToSend.currencySymbol + } else { + "" + }, + amount = amountToSend.value?.stripZeroPlainString() ?: "0", + fee = feeAmount.value?.stripZeroPlainString() ?: "0", + ), + ) + } else { + store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError( + walletManager = walletManager, + amountToSend = amountToSend, + feeAmount = feeAmount, + destinationAddress = destinationAddress, + ) + } } fun createValidateTransactionError( From 2fcc9abc8e87234d6065114ff82ad09c2a1ec42b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 27 May 2024 13:11:47 +0500 Subject: [PATCH 95/98] Updated on 2026-08-14 --- .../tangem/utils/coroutines/CoroutineExt.kt | 7 ++++++ .../presentation/viewmodel/SendViewModel.kt | 24 ++++++++++--------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt index ebbe1dc50a..cfa0536804 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt @@ -10,6 +10,13 @@ suspend fun runCatching(dispatcher: CoroutineDispatcher, block: suspend () - } } +suspend fun waitForDelay(delay: Long, block: suspend CoroutineScope.() -> R): R = coroutineScope { + val minWaitingTime = async { delay(delay) } + val actionInvoke = async { block() } + minWaitingTime.await() + actionInvoke.await() +} + class Debouncer { private var debounceJob: Job? = null diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 5b9d5f336f..209e301af2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -53,10 +53,7 @@ import com.tangem.features.send.impl.presentation.state.fee.* import com.tangem.features.send.impl.presentation.state.recipient.RecipientSendFactory import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.DelayedWork -import com.tangem.utils.coroutines.JobHolder -import com.tangem.utils.coroutines.saveIn +import com.tangem.utils.coroutines.* import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* @@ -412,9 +409,11 @@ internal class SendViewModel @Inject constructor( private fun getUserWallets() { viewModelScope.launch(dispatchers.main) { runCatching { - getWalletsUseCase.invokeSync() - ?.toAvailableWallets() - .orEmpty() + waitForDelay(delay = RECENT_LOAD_DELAY) { + getWalletsUseCase.invokeSync() + ?.toAvailableWallets() + .orEmpty() + } }.onSuccess { result -> userWallets = result uiState = recipientStateFactory.onLoadedWalletsList(wallets = userWallets) @@ -455,10 +454,12 @@ internal class SendViewModel @Inject constructor( } private suspend fun getTxHistory() { - val txHistoryList = getFixedTxHistoryItemsUseCase.getSync( - userWalletId = userWalletId, - currency = cryptoCurrency, - ).getOrElse { emptyList() } + val txHistoryList = waitForDelay(delay = RECENT_LOAD_DELAY) { + getFixedTxHistoryItemsUseCase.getSync( + userWalletId = userWalletId, + currency = cryptoCurrency, + ).getOrElse { emptyList() } + } uiState = recipientStateFactory.onLoadedHistoryList(txHistory = txHistoryList) } @@ -971,6 +972,7 @@ internal class SendViewModel @Inject constructor( private companion object { const val CHECK_FEE_UPDATE_DELAY = 60_000L const val BALANCE_UPDATE_DELAY = 11_000L + const val RECENT_LOAD_DELAY = 500L const val RU_LOCALE = "ru" const val EN_LOCALE = "en" From 7aee782003d6e5fd099c215724c8b3c695b9eeb5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 27 May 2024 13:12:58 +0500 Subject: [PATCH 96/98] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 11 +++++++++-- core/res/src/main/res/values/strings.xml | 5 ++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 53f6a46e4e..23dd6a8a8c 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -233,6 +233,7 @@ Требуется разрешение Условиями использования Токены не найдены. Пожалуйста, попробуйте другой запрос + ID: %s ID транзакции скопирован Информация ниже не является обязательной. Вы можете стереть её, если хотите. Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. @@ -380,7 +381,9 @@ Сортировка токенов Список Выбрать из галереи - Нравится наше приложение? + Настройки + Вы не предоставили доступ к вашей камере + Доступ к камере запрещен %1$s (%2$s) в сети %3$s Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств. Участвовать @@ -435,7 +438,7 @@ Сканировать Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. Приготовьте свою карту - Уже содержится в введенном адресе + Уже содержится во введенном адресе Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? Причина: %1$s\nКод: %2$s @@ -510,6 +513,9 @@ Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время Неверный адрес Транзакция отправлена + Забыть кошелек + Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова. + Имя Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. Революционный аппаратный кошелек До трех карт с одним кошельком @@ -633,6 +639,7 @@ Сеть %s Адрес скопирован в буфер обмена Нет соединения с интернетом + Настройки кошелька Tangem Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 4f7f952256..99df15650b 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -379,7 +379,6 @@ Settings You have not given access to your camera Camera access denied - Enjoying our app? %1$s (%2$s) on %3$s network Send only %s to this address. Sending any other currency will result in its irreversible loss. Participate @@ -510,6 +509,9 @@ Invalid address %1$s (%2$s) Transaction sent + Forget wallet + This will remove the wallet from the application. The wallet itself can be added again. + Name Store your crypto assets secure while keeping private keys contained in your card Revolutionary Hardware Wallet Up to 3 physical cards to one wallet @@ -633,6 +635,7 @@ %s network Address was copied to clipboard No internet connection + Wallet settings Tangem Use %s or scan a card to unlock access to your wallet Please withdraw all funds from this wallet, reset it to factory settings, and create a new one. Access to the current wallet will be lost. From 125b301a3223c2ade40bb2acccb356a023ff3a65 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 May 2024 14:29:37 +0800 Subject: [PATCH 97/98] Updated on 2026-08-14 --- .../details/redux/DetailsMiddleware.kt | 63 +++++++++++++++---- .../com/tangem/utils/extensions/Collection.kt | 5 ++ .../wallet/viewmodels/WalletViewModel.kt | 23 +++++-- 3 files changed, 74 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 495afd0915..372889fe91 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -17,8 +17,9 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.domain.wallets.builder.UserWalletIdBuilder +import com.tangem.domain.wallets.legacy.UserWalletsListError import com.tangem.domain.wallets.legacy.asLockable -import com.tangem.tap.* +import com.tangem.domain.wallets.models.UserWallet import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.* @@ -29,6 +30,9 @@ 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.proxy.redux.DaggerGraphState +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn import com.tangem.wallet.R @@ -503,37 +507,70 @@ class DetailsMiddleware { store.dispatchOnMain(NavigationAction.PopBackTo()) }, onSuccess = { scanResponse -> - saveUserWalletAndPopBackToWalletScreen(scanResponse) + createUserWallet(scanResponse) + .doOnSuccess { + saveUserWalletAndPopBackToWalletScreen( + userWallet = it, + prevUseBiometricsForAccessCode = prevUseBiometricsForAccessCode, + ) + } .doOnFailure { error -> - // Rollback policy if card saving was failed - cardSdkConfigRepository.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) - Timber.e(error, "Unable to save user wallet") - - store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Error(error.toTextReference())) + Timber.e(error, "Unable to create user wallet") + handleError(error = error, prevUseBiometricsForAccessCode = prevUseBiometricsForAccessCode) } }, onFailure = { error -> - // Rollback policy if card scanning was failed - cardSdkConfigRepository.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) Timber.e(error, "Unable to scan card") - store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Error(error.toTextReference())) + handleError(error = error, prevUseBiometricsForAccessCode = prevUseBiometricsForAccessCode) }, ) } - private suspend fun saveUserWalletAndPopBackToWalletScreen(scanResponse: ScanResponse): CompletionResult { + private suspend fun createUserWallet(scanResponse: ScanResponse): CompletionResult { val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase) val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase).build() - ?: return CompletionResult.Failure(TangemSdkError.WalletIsNotCreated()) + return if (userWallet != null) { + CompletionResult.Success(userWallet) + } else { + CompletionResult.Failure(TangemSdkError.WalletIsNotCreated()) + } + } + + private suspend fun saveUserWalletAndPopBackToWalletScreen( + userWallet: UserWallet, + prevUseBiometricsForAccessCode: Boolean, + ) { val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) - return userWalletsListManager.save(userWallet) + + userWalletsListManager.save(userWallet) .doOnSuccess { store.onUserWalletSelected(userWallet) store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success) store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet)) } + .doOnFailure { error -> + if (error is UserWalletsListError.WalletAlreadySaved) { + userWalletsListManager.select(userWallet.walletId) + store.onUserWalletSelected(userWallet) + + store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success) + store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + } else { + Timber.e(error, "Unable to create user wallet") + handleError(error, prevUseBiometricsForAccessCode) + } + } + } + + private suspend fun handleError(error: TangemError, prevUseBiometricsForAccessCode: Boolean) { + val cardSdkConfigRepository = store.inject(DaggerGraphState::cardSdkConfigRepository) + + // Rollback policy if card saving was failed + cardSdkConfigRepository.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) + + store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Error(error.toTextReference())) } private fun TangemError.toTextReference(): TextReference? { diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt b/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt index 9930c5cb77..6ab240ea41 100644 --- a/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt +++ b/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt @@ -18,4 +18,9 @@ fun Collection.isSingleItem(): Boolean = this.size == 1 */ fun Collection.copy(): Collection { return this.map { it } +} + +inline fun List.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? { + val index = indexOfFirst(predicate) + return if (index == -1) null else index } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 912b4c81a3..44d391e849 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -9,6 +9,7 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.settings.CanUseBiometryUseCase import com.tangem.domain.settings.IsWalletsScrollPreviewEnabled import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler @@ -31,6 +32,7 @@ import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn +import com.tangem.utils.extensions.indexOfFirstOrNull import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* @@ -153,16 +155,29 @@ internal class WalletViewModel @Inject constructor( selectedWalletAnalyticsSender.send(selectedWallet) } - walletDeepLinksHandler.registerForWallet( - viewModel = this, - userWallet = selectedWallet, - ) + changeSelectedWalletState(selectedWalletId = selectedWallet.walletId) + + walletDeepLinksHandler.registerForWallet(viewModel = this, userWallet = selectedWallet) } .flowOn(dispatchers.main) .launchIn(viewModelScope) } } + /** Change selected wallet state if selected wallet [selectedWalletId] was changed in the background */ + private suspend fun changeSelectedWalletState(selectedWalletId: UserWalletId) { + if (screenLifecycleProvider.isBackgroundState.value && selectedWalletId != stateHolder.getSelectedWalletId()) { + stateHolder.value.wallets + .indexOfFirstOrNull { prevState -> prevState.walletCardState.id == selectedWalletId } + ?.let { selectedIndex -> + Timber.e("Selected wallet changed from background state: $selectedWalletId") + + delay(timeMillis = 1000) + scrollToWallet(selectedIndex) + } + } + } + // We need to update the current wallet if the application was in the background for more than 10 seconds // and then returned to the foreground private fun subscribeToScreenBackgroundState() { From a342849277ca8f8c6cdf337ce044e6d961c00d6f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 27 May 2024 17:47:25 +0800 Subject: [PATCH 98/98] Updated on 2026-08-14 --- .../ui/resetcard/di/ResetCardModule.kt | 21 +++++++++++++++++++ .../DefaultResetCardFeatureToggles.kt | 11 ++++++++++ .../featuretoggles/ResetCardFeatureToggles.kt | 6 ++++++ .../configs/feature_toggles_config.json | 4 ++++ 4 files changed, 42 insertions(+) create mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/resetcard/di/ResetCardModule.kt create mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/DefaultResetCardFeatureToggles.kt create mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/ResetCardFeatureToggles.kt diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/di/ResetCardModule.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/di/ResetCardModule.kt new file mode 100644 index 0000000000..2de1e70012 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/di/ResetCardModule.kt @@ -0,0 +1,21 @@ +package com.tangem.tap.features.details.ui.resetcard.di + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.tap.features.details.ui.resetcard.featuretoggles.DefaultResetCardFeatureToggles +import com.tangem.tap.features.details.ui.resetcard.featuretoggles.ResetCardFeatureToggles +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object ResetCardModule { + + @Provides + @Singleton + fun provideResetCardFeatureToggles(featureTogglesManager: FeatureTogglesManager): ResetCardFeatureToggles { + return DefaultResetCardFeatureToggles(featureTogglesManager) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/DefaultResetCardFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/DefaultResetCardFeatureToggles.kt new file mode 100644 index 0000000000..a5b76a410d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/DefaultResetCardFeatureToggles.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.features.details.ui.resetcard.featuretoggles + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager + +internal class DefaultResetCardFeatureToggles( + private val featureTogglesManager: FeatureTogglesManager, +) : ResetCardFeatureToggles { + + override val isFullResetEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled(name = "FULL_RESET_ENABLED") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/ResetCardFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/ResetCardFeatureToggles.kt new file mode 100644 index 0000000000..d290d4d1d9 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/ResetCardFeatureToggles.kt @@ -0,0 +1,6 @@ +package com.tangem.tap.features.details.ui.resetcard.featuretoggles + +interface ResetCardFeatureToggles { + + val isFullResetEnabled: Boolean +} \ No newline at end of file diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 2bb2538503..19da1d1f14 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -34,5 +34,9 @@ { "name": "STAKING_ENABLED", "version": "undefined" + }, + { + "name": "FULL_RESET_ENABLED", + "version": "5.12.0" } ]