Updated on 2026-08-14
This commit is contained in:
parent
8b69fb70dc
commit
6a664f4a52
25 changed files with 762 additions and 7 deletions
|
|
@ -5,6 +5,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam
|
import com.tangem.core.analytics.models.AnalyticsParam
|
||||||
import com.tangem.core.analytics.models.event.SignIn
|
import com.tangem.core.analytics.models.event.SignIn
|
||||||
import com.tangem.domain.card.analytics.IntroductionProcess
|
import com.tangem.domain.card.analytics.IntroductionProcess
|
||||||
|
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
|
||||||
|
|
||||||
class HotWalletContextInterceptor(
|
class HotWalletContextInterceptor(
|
||||||
val parent: ParamsInterceptor? = null,
|
val parent: ParamsInterceptor? = null,
|
||||||
|
|
@ -18,6 +19,7 @@ class HotWalletContextInterceptor(
|
||||||
is SignIn.ButtonAddWallet,
|
is SignIn.ButtonAddWallet,
|
||||||
is SignIn.ButtonUnlockAllWithBiometric,
|
is SignIn.ButtonUnlockAllWithBiometric,
|
||||||
is IntroductionProcess.ButtonScanCard,
|
is IntroductionProcess.ButtonScanCard,
|
||||||
|
is TokenScreenAnalyticsEvent.ButtonQuickTopUp,
|
||||||
-> false
|
-> false
|
||||||
is SignIn.ErrorBiometricUpdated -> !event.isFromUnlockAll
|
is SignIn.ErrorBiometricUpdated -> !event.isFromUnlockAll
|
||||||
else -> true
|
else -> true
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
userWalletId = route.userWalletId,
|
userWalletId = route.userWalletId,
|
||||||
cryptoCurrency = route.currency,
|
cryptoCurrency = route.currency,
|
||||||
source = route.source,
|
source = route.source,
|
||||||
|
initialFiatAmount = route.initialFiatAmount,
|
||||||
),
|
),
|
||||||
componentFactory = onrampComponentFactory,
|
componentFactory = onrampComponentFactory,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -297,6 +297,7 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
val source: OnrampSource,
|
val source: OnrampSource,
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
val currency: CryptoCurrency,
|
val currency: CryptoCurrency,
|
||||||
|
val initialFiatAmount: SerializedBigDecimal? = null,
|
||||||
) : AppRoute(path = "/onramp/${userWalletId.stringValue}/${currency.symbol}"), RouteBundleParams {
|
) : AppRoute(path = "/onramp/${userWalletId.stringValue}/${currency.symbol}"), RouteBundleParams {
|
||||||
override fun getBundle(): Bundle = bundle(serializer())
|
override fun getBundle(): Bundle = bundle(serializer())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -328,6 +328,7 @@ sealed class AnalyticsParam {
|
||||||
const val WALLET_TYPE = "Wallet Type"
|
const val WALLET_TYPE = "Wallet Type"
|
||||||
const val BACKUPED = "Backuped"
|
const val BACKUPED = "Backuped"
|
||||||
const val MEMO = "Memo"
|
const val MEMO = "Memo"
|
||||||
|
const val VALUE = "Value"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,5 +114,9 @@
|
||||||
{
|
{
|
||||||
"name": "AND_15438_BACKEND_AUTHENTICATION_ENABLED",
|
"name": "AND_15438_BACKEND_AUTHENTICATION_ENABLED",
|
||||||
"version": "undefined"
|
"version": "undefined"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AND_15258_QUICK_TOP_UP_ENABLED",
|
||||||
|
"version": "undefined"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,5 @@ object StringsSigns {
|
||||||
const val PASSWORD_VISUAL_CHAR = '\u2022'
|
const val PASSWORD_VISUAL_CHAR = '\u2022'
|
||||||
const val APPROXIMATE = "≈"
|
const val APPROXIMATE = "≈"
|
||||||
const val WHITE_SPACE = " "
|
const val WHITE_SPACE = " "
|
||||||
|
const val LIGHTNING = "⚡"
|
||||||
}
|
}
|
||||||
|
|
@ -5,8 +5,10 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.ACCOUNT_DERIVATION_FR
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ACTION
|
import com.tangem.core.analytics.models.AnalyticsParam.Key.ACTION
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.BALANCE
|
import com.tangem.core.analytics.models.AnalyticsParam.Key.BALANCE
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
|
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsParam.Key.CURRENCY
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.STATUS
|
import com.tangem.core.analytics.models.AnalyticsParam.Key.STATUS
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsParam.Key.VALUE
|
||||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -181,6 +183,21 @@ sealed class TokenScreenAnalyticsEvent(
|
||||||
params = mapOf("Token" to token),
|
params = mapOf("Token" to token),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class ButtonQuickTopUp(
|
||||||
|
token: String,
|
||||||
|
blockchain: String,
|
||||||
|
currency: String,
|
||||||
|
value: String,
|
||||||
|
) : TokenScreenAnalyticsEvent(
|
||||||
|
event = "Quick Top Up Button",
|
||||||
|
params = mapOf(
|
||||||
|
TOKEN_PARAM to token,
|
||||||
|
BLOCKCHAIN to blockchain,
|
||||||
|
CURRENCY to currency,
|
||||||
|
VALUE to value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val AVAILABLE = "Available"
|
const val AVAILABLE = "Available"
|
||||||
private const val UNAVAILABLE = "Unavailable"
|
private const val UNAVAILABLE = "Unavailable"
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.onramp.model.OnrampSource
|
import com.tangem.domain.onramp.model.OnrampSource
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
interface OnrampComponent : ComposableContentComponent {
|
interface OnrampComponent : ComposableContentComponent {
|
||||||
|
|
||||||
|
|
@ -12,6 +13,7 @@ interface OnrampComponent : ComposableContentComponent {
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
val cryptoCurrency: CryptoCurrency,
|
val cryptoCurrency: CryptoCurrency,
|
||||||
val source: OnrampSource,
|
val source: OnrampSource,
|
||||||
|
val initialFiatAmount: BigDecimal? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface Factory : ComponentFactory<Params, OnrampComponent>
|
interface Factory : ComponentFactory<Params, OnrampComponent>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||||
import com.tangem.domain.onramp.model.OnrampSource
|
import com.tangem.domain.onramp.model.OnrampSource
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
internal interface OnrampMainComponent : ComposableContentComponent {
|
internal interface OnrampMainComponent : ComposableContentComponent {
|
||||||
|
|
||||||
|
|
@ -15,6 +16,7 @@ internal interface OnrampMainComponent : ComposableContentComponent {
|
||||||
val source: OnrampSource,
|
val source: OnrampSource,
|
||||||
val openSettings: () -> Unit,
|
val openSettings: () -> Unit,
|
||||||
val openRedirectPage: (quote: OnrampProviderWithQuote.Data) -> Unit,
|
val openRedirectPage: (quote: OnrampProviderWithQuote.Data) -> Unit,
|
||||||
|
val initialFiatAmount: BigDecimal? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface Factory : ComponentFactory<Params, OnrampMainComponent>
|
interface Factory : ComponentFactory<Params, OnrampMainComponent>
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ internal class OnrampStateFactory(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getReadyState(currency: OnrampCurrency): OnrampMainComponentUM.Content {
|
fun getReadyState(currency: OnrampCurrency, initialFiatAmount: BigDecimal? = null): OnrampMainComponentUM.Content {
|
||||||
val state = currentStateProvider()
|
val state = currentStateProvider()
|
||||||
|
|
||||||
val endButton = when (val button = state.topBarConfig.endButtonUM) {
|
val endButton = when (val button = state.topBarConfig.endButtonUM) {
|
||||||
|
|
@ -59,7 +59,7 @@ internal class OnrampStateFactory(
|
||||||
is TopAppBarButtonUM.Text -> button.copy(isEnabled = true)
|
is TopAppBarButtonUM.Text -> button.copy(isEnabled = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
val initialAmountBlockState = getInitialAmountBlockState(currency)
|
val initialAmountBlockState = getInitialAmountBlockState(currency, initialFiatAmount)
|
||||||
|
|
||||||
return OnrampMainComponentUM.Content(
|
return OnrampMainComponentUM.Content(
|
||||||
topBarConfig = state.topBarConfig.copy(endButtonUM = endButton),
|
topBarConfig = state.topBarConfig.copy(endButtonUM = endButton),
|
||||||
|
|
@ -136,7 +136,10 @@ internal class OnrampStateFactory(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getInitialAmountBlockState(currency: OnrampCurrency): OnrampAmountBlockUM {
|
private fun getInitialAmountBlockState(
|
||||||
|
currency: OnrampCurrency,
|
||||||
|
initialFiatAmount: BigDecimal? = null,
|
||||||
|
): OnrampAmountBlockUM {
|
||||||
return OnrampAmountBlockUM(
|
return OnrampAmountBlockUM(
|
||||||
currencyUM = OnrampCurrencyUM(
|
currencyUM = OnrampCurrencyUM(
|
||||||
code = currency.code,
|
code = currency.code,
|
||||||
|
|
@ -146,8 +149,8 @@ internal class OnrampStateFactory(
|
||||||
unit = currency.unit,
|
unit = currency.unit,
|
||||||
),
|
),
|
||||||
amountFieldModel = AmountFieldModel(
|
amountFieldModel = AmountFieldModel(
|
||||||
value = "",
|
value = initialFiatAmount?.toPlainString().orEmpty(),
|
||||||
fiatValue = "",
|
fiatValue = initialFiatAmount?.toPlainString().orEmpty(),
|
||||||
onValueChange = onrampIntents::onAmountValueChanged,
|
onValueChange = onrampIntents::onAmountValueChanged,
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
imeAction = ImeAction.None,
|
imeAction = ImeAction.None,
|
||||||
|
|
@ -156,7 +159,7 @@ internal class OnrampStateFactory(
|
||||||
keyboardActions = KeyboardActions(),
|
keyboardActions = KeyboardActions(),
|
||||||
isFiatValue = true,
|
isFiatValue = true,
|
||||||
cryptoAmount = BigDecimal.ZERO.convertToAmount(cryptoCurrency),
|
cryptoAmount = BigDecimal.ZERO.convertToAmount(cryptoCurrency),
|
||||||
fiatAmount = BigDecimal.ZERO.convertToFiatAmount(currency),
|
fiatAmount = (initialFiatAmount ?: BigDecimal.ZERO).convertToFiatAmount(currency),
|
||||||
isError = false,
|
isError = false,
|
||||||
isWarning = false,
|
isWarning = false,
|
||||||
error = TextReference.EMPTY,
|
error = TextReference.EMPTY,
|
||||||
|
|
|
||||||
|
|
@ -274,7 +274,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
||||||
amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency)
|
amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency)
|
||||||
}
|
}
|
||||||
is OnrampMainComponentUM.InitialLoading -> {
|
is OnrampMainComponentUM.InitialLoading -> {
|
||||||
stateFactory.getReadyState(country.defaultCurrency)
|
stateFactory.getReadyState(country.defaultCurrency, params.initialFiatAmount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ internal class DefaultOnrampComponent @AssistedInject constructor(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
initialFiatAmount = params.initialFiatAmount,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
is OnrampChild.RedirectPage -> onrampRedirectComponentFactory.create(
|
is OnrampChild.RedirectPage -> onrampRedirectComponentFactory.create(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.tangem.features.tokendetails
|
||||||
|
|
||||||
|
interface TokenDetailsFeatureToggles {
|
||||||
|
val isQuickTopUpEnabled: Boolean
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.tangem.feature.tokendetails
|
||||||
|
|
||||||
|
import com.tangem.core.configtoggle.FeatureToggles
|
||||||
|
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||||
|
import com.tangem.features.tokendetails.TokenDetailsFeatureToggles
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class DefaultTokenDetailsFeatureToggles @Inject constructor(
|
||||||
|
featureTogglesManager: FeatureTogglesManager,
|
||||||
|
) : TokenDetailsFeatureToggles {
|
||||||
|
|
||||||
|
override val isQuickTopUpEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
|
||||||
|
toggle = FeatureToggles.AND_15258_QUICK_TOP_UP_ENABLED,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.feature.tokendetails.di
|
||||||
|
|
||||||
|
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||||
|
import com.tangem.feature.tokendetails.DefaultTokenDetailsFeatureToggles
|
||||||
|
import com.tangem.features.tokendetails.TokenDetailsFeatureToggles
|
||||||
|
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 TokenDetailsFeatureModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideTokenDetailsFeatureToggles(featureTogglesManager: FeatureTogglesManager): TokenDetailsFeatureToggles {
|
||||||
|
return DefaultTokenDetailsFeatureToggles(featureTogglesManager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceSegmentedButtonConfig
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceSegmentedButtonConfig
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
interface TokenDetailsClickIntents {
|
interface TokenDetailsClickIntents {
|
||||||
|
|
@ -71,6 +72,8 @@ interface TokenDetailsClickIntents {
|
||||||
|
|
||||||
fun onBalanceSelect(config: TokenBalanceSegmentedButtonConfig)
|
fun onBalanceSelect(config: TokenBalanceSegmentedButtonConfig)
|
||||||
|
|
||||||
|
fun onQuickTopUpClick(amount: BigDecimal, currencyCode: String)
|
||||||
|
|
||||||
fun onYieldInfoClick()
|
fun onYieldInfoClick()
|
||||||
|
|
||||||
// region Clore migration
|
// region Clore migration
|
||||||
|
|
@ -168,6 +171,8 @@ internal class EmptyTokenDetailsClickIntents : TokenDetailsClickIntents {
|
||||||
|
|
||||||
override fun onYieldInfoClick() { /* no op */ }
|
override fun onYieldInfoClick() { /* no op */ }
|
||||||
|
|
||||||
|
override fun onQuickTopUpClick(amount: BigDecimal, currencyCode: String) { /* no op */ }
|
||||||
|
|
||||||
override fun onCopyAddress(): TextReference? {
|
override fun onCopyAddress(): TextReference? {
|
||||||
/* no op */
|
/* no op */
|
||||||
return null
|
return null
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ import com.tangem.domain.models.network.NetworkAddress
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.offramp.GetOfframpUrlUseCase
|
import com.tangem.domain.offramp.GetOfframpUrlUseCase
|
||||||
|
import com.tangem.domain.onramp.CheckOnrampAvailabilityUseCase
|
||||||
import com.tangem.domain.onramp.model.OnrampSource
|
import com.tangem.domain.onramp.model.OnrampSource
|
||||||
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
|
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
|
||||||
import com.tangem.domain.staking.GetStakingEntryInfoUseCase
|
import com.tangem.domain.staking.GetStakingEntryInfoUseCase
|
||||||
|
|
@ -84,6 +85,7 @@ import com.tangem.domain.transaction.error.OpenTrustlineError
|
||||||
import com.tangem.domain.transaction.error.SendTransactionError
|
import com.tangem.domain.transaction.error.SendTransactionError
|
||||||
import com.tangem.domain.transaction.usecase.*
|
import com.tangem.domain.transaction.usecase.*
|
||||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||||
|
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||||
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
|
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
|
||||||
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
|
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
|
||||||
import com.tangem.domain.wallets.usecase.GetExtendedPublicKeyForCurrencyUseCase
|
import com.tangem.domain.wallets.usecase.GetExtendedPublicKeyForCurrencyUseCase
|
||||||
|
|
@ -103,6 +105,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsStateController
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsStateController
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsUM
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsUM
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TransferUM
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TransferUM
|
||||||
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.QuickTopUpBlockFactory
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer.InitializeWithCryptoCurrencyTransformer
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer.InitializeWithCryptoCurrencyTransformer
|
||||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer.SetBalanceTransformer
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer.SetBalanceTransformer
|
||||||
|
|
@ -132,6 +135,7 @@ import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.math.BigDecimal
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions", "PropertyUsedBeforeDeclaration")
|
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions", "PropertyUsedBeforeDeclaration")
|
||||||
|
|
@ -192,6 +196,9 @@ internal class TokenDetailsModel @Inject constructor(
|
||||||
private val redesignStateController: TokenDetailsStateController,
|
private val redesignStateController: TokenDetailsStateController,
|
||||||
private val swapFeedbackUseCase: SwapFeedbackUseCase,
|
private val swapFeedbackUseCase: SwapFeedbackUseCase,
|
||||||
private val swapFeatureToggles: SwapFeatureToggles,
|
private val swapFeatureToggles: SwapFeatureToggles,
|
||||||
|
private val quickTopUpBlockFactory: QuickTopUpBlockFactory,
|
||||||
|
private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||||
|
private val checkOnrampAvailabilityUseCase: CheckOnrampAvailabilityUseCase,
|
||||||
) : Model(),
|
) : Model(),
|
||||||
TokenDetailsClickIntents,
|
TokenDetailsClickIntents,
|
||||||
YieldSupplyDepositedWarningComponent.ModelCallback {
|
YieldSupplyDepositedWarningComponent.ModelCallback {
|
||||||
|
|
@ -304,6 +311,7 @@ internal class TokenDetailsModel @Inject constructor(
|
||||||
handleBalanceHiding()
|
handleBalanceHiding()
|
||||||
checkForActionUpdates()
|
checkForActionUpdates()
|
||||||
handleNavigationParam()
|
handleNavigationParam()
|
||||||
|
observeQuickTopUpBlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initButtons() {
|
private fun initButtons() {
|
||||||
|
|
@ -584,6 +592,43 @@ internal class TokenDetailsModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onQuickTopUpClick(amount: BigDecimal, currencyCode: String) {
|
||||||
|
analyticsEventsHandler.send(
|
||||||
|
TokenScreenAnalyticsEvent.ButtonQuickTopUp(
|
||||||
|
token = cryptoCurrency.symbol,
|
||||||
|
blockchain = cryptoCurrency.network.name,
|
||||||
|
currency = currencyCode,
|
||||||
|
value = amount.toInt().toString(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
appRouter.push(
|
||||||
|
AppRoute.Onramp(
|
||||||
|
source = OnrampSource.TOKEN_DETAILS,
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
currency = cryptoCurrency,
|
||||||
|
initialFiatAmount = amount,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onQuickTopUpOtherClick() {
|
||||||
|
analyticsEventsHandler.send(
|
||||||
|
TokenScreenAnalyticsEvent.ButtonWithParams.ButtonBuy(
|
||||||
|
token = cryptoCurrency.symbol,
|
||||||
|
blockchain = cryptoCurrency.network.name,
|
||||||
|
status = ScenarioUnavailabilityReason.None.toReasonAnalyticsText(),
|
||||||
|
derivationIndex = getAccountIndexOrNull(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
appRouter.push(
|
||||||
|
AppRoute.Onramp(
|
||||||
|
source = OnrampSource.TOKEN_DETAILS,
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
currency = cryptoCurrency,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBuyCoinClick(cryptoCurrency: CryptoCurrency) {
|
override fun onBuyCoinClick(cryptoCurrency: CryptoCurrency) {
|
||||||
analyticsEventsHandler.send(
|
analyticsEventsHandler.send(
|
||||||
TokenScreenAnalyticsEvent.ButtonWithParams.ButtonBuy(
|
TokenScreenAnalyticsEvent.ButtonWithParams.ButtonBuy(
|
||||||
|
|
@ -1363,6 +1408,38 @@ internal class TokenDetailsModel @Inject constructor(
|
||||||
observeRedesignStakingNotification()
|
observeRedesignStakingNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun observeQuickTopUpBlock() {
|
||||||
|
getAccountCryptoCurrencyStatusUseCase(userWalletId, cryptoCurrency)
|
||||||
|
.map { it.status }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.flatMapLatest { status ->
|
||||||
|
flow {
|
||||||
|
val amount = status.value.amount
|
||||||
|
if (amount == null || !amount.isZero()) {
|
||||||
|
emit(null)
|
||||||
|
return@flow
|
||||||
|
}
|
||||||
|
val txCount = getTxHistoryItemsCountUseCase(userWalletId, cryptoCurrency)
|
||||||
|
val availability = checkOnrampAvailabilityUseCase(userWallet)
|
||||||
|
emit(
|
||||||
|
quickTopUpBlockFactory.build(
|
||||||
|
currencyStatus = status,
|
||||||
|
isTxHistoryEmpty = txCount,
|
||||||
|
onrampAvailability = availability,
|
||||||
|
onPresetClick = ::onQuickTopUpClick,
|
||||||
|
onOtherClick = ::onQuickTopUpOtherClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onEach { block ->
|
||||||
|
uiState.value = uiState.value.copy(quickTopUpBlock = block)
|
||||||
|
redesignStateController.update { state -> state.copy(quickTopUpBlock = block) }
|
||||||
|
}
|
||||||
|
.flowOn(dispatchers.default)
|
||||||
|
.launchIn(modelScope)
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeRedesignStakingNotification() {
|
private fun observeRedesignStakingNotification() {
|
||||||
val statusFlow = getAccountCryptoCurrencyStatusUseCase(userWalletId, cryptoCurrency)
|
val statusFlow = getAccountCryptoCurrencyStatusUseCase(userWalletId, cryptoCurrency)
|
||||||
.map { it.status }
|
.map { it.status }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.tangem.feature.tokendetails.presentation.tokendetails.state
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
internal data class QuickTopUpBlockUM(
|
||||||
|
val amounts: ImmutableList<QuickTopUpAmountUM>,
|
||||||
|
) {
|
||||||
|
@Immutable
|
||||||
|
data class QuickTopUpAmountUM(
|
||||||
|
val displayValue: TextReference,
|
||||||
|
val onClick: () -> Unit,
|
||||||
|
val isOther: Boolean = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -15,4 +15,5 @@ internal data class TokenDetailsState(
|
||||||
val pullToRefreshConfig: PullToRefreshConfig,
|
val pullToRefreshConfig: PullToRefreshConfig,
|
||||||
val isBalanceHidden: Boolean,
|
val isBalanceHidden: Boolean,
|
||||||
val isMarketPriceAvailable: Boolean,
|
val isMarketPriceAvailable: Boolean,
|
||||||
|
val quickTopUpBlock: QuickTopUpBlockUM? = null,
|
||||||
)
|
)
|
||||||
|
|
@ -24,6 +24,7 @@ internal data class TokenDetailsUM(
|
||||||
val addFundsUM: AddFundsUM,
|
val addFundsUM: AddFundsUM,
|
||||||
val transferUM: TransferUM,
|
val transferUM: TransferUM,
|
||||||
val zeroBalanceActionsUM: ZeroBalanceActionsUM,
|
val zeroBalanceActionsUM: ZeroBalanceActionsUM,
|
||||||
|
val quickTopUpBlock: QuickTopUpBlockUM? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.tangem.core.res.R
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
|
import com.tangem.domain.onramp.model.OnrampAvailability
|
||||||
|
import com.tangem.domain.onramp.model.error.OnrampError
|
||||||
|
import com.tangem.domain.txhistory.models.TxHistoryStateError
|
||||||
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.QuickTopUpBlockUM
|
||||||
|
import com.tangem.features.tokendetails.TokenDetailsFeatureToggles
|
||||||
|
import com.tangem.utils.extensions.isZero
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class QuickTopUpBlockFactory @Inject constructor(
|
||||||
|
private val featureToggles: TokenDetailsFeatureToggles,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun build(
|
||||||
|
currencyStatus: CryptoCurrencyStatus,
|
||||||
|
isTxHistoryEmpty: Either<TxHistoryStateError, Int>,
|
||||||
|
onrampAvailability: Either<OnrampError, OnrampAvailability>,
|
||||||
|
onPresetClick: (BigDecimal, String) -> Unit,
|
||||||
|
onOtherClick: () -> Unit,
|
||||||
|
): QuickTopUpBlockUM? {
|
||||||
|
if (!featureToggles.isQuickTopUpEnabled) return null
|
||||||
|
|
||||||
|
val amount = currencyStatus.value.amount
|
||||||
|
if (amount == null || !amount.isZero()) return null
|
||||||
|
|
||||||
|
val isHistoryEmpty = isTxHistoryEmpty.fold(
|
||||||
|
ifLeft = { it is TxHistoryStateError.EmptyTxHistories },
|
||||||
|
ifRight = { it == 0 },
|
||||||
|
)
|
||||||
|
if (!isHistoryEmpty) return null
|
||||||
|
|
||||||
|
val currency = when (val availability = onrampAvailability.getOrNull()) {
|
||||||
|
is OnrampAvailability.Available -> availability.currency
|
||||||
|
is OnrampAvailability.ConfirmResidency -> {
|
||||||
|
if (!availability.country.onrampAvailable) return null
|
||||||
|
availability.country.defaultCurrency
|
||||||
|
}
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val presets = when (currency.code) {
|
||||||
|
USD_CODE -> USD_PRESETS
|
||||||
|
EUR_CODE -> EUR_PRESETS
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val presetAmounts = presets.map { value ->
|
||||||
|
QuickTopUpBlockUM.QuickTopUpAmountUM(
|
||||||
|
displayValue = stringReference("${currency.unit}$value"),
|
||||||
|
onClick = { onPresetClick(BigDecimal(value), currency.code) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val otherAmount = QuickTopUpBlockUM.QuickTopUpAmountUM(
|
||||||
|
displayValue = resourceReference(R.string.quick_top_up_chip_other),
|
||||||
|
onClick = onOtherClick,
|
||||||
|
isOther = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
return QuickTopUpBlockUM(
|
||||||
|
amounts = (presetAmounts + otherAmount).toImmutableList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val USD_CODE = "USD"
|
||||||
|
const val EUR_CODE = "EUR"
|
||||||
|
|
||||||
|
val USD_PRESETS = listOf(50, 200, 700)
|
||||||
|
val EUR_PRESETS = listOf(50, 200, 650)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
package com.tangem.feature.tokendetails.presentation.tokendetails.ui
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.BlendMode
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.util.fastForEach
|
||||||
|
import com.tangem.core.res.R
|
||||||
|
import com.tangem.core.ui.extensions.combinedReference
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.utils.StringsSigns
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||||
|
import com.tangem.feature.tokendetails.presentation.tokendetails.state.QuickTopUpBlockUM
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
private val quickTopUpGradientBrush = Brush.linearGradient(
|
||||||
|
colors = listOf(Color(0xFFEDE5F3), Color(0xFFD7EDD9)),
|
||||||
|
start = Offset(0f, 0f),
|
||||||
|
end = Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val quickTopUpBorderBrush = Brush.sweepGradient(
|
||||||
|
listOf(
|
||||||
|
Color(0x0D000000),
|
||||||
|
Color(0x26000000),
|
||||||
|
Color(0x0D000000),
|
||||||
|
Color(0x26000000),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun QuickTopUpBlock(state: QuickTopUpBlockUM, modifier: Modifier = Modifier) {
|
||||||
|
val shape = RoundedCornerShape(TangemTheme.dimens.radius20)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(shape)
|
||||||
|
.background(brush = quickTopUpGradientBrush)
|
||||||
|
.border(width = 1.dp, brush = quickTopUpBorderBrush, shape = shape),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(TangemTheme.dimens.spacing12),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||||
|
) {
|
||||||
|
val textColor = TangemTheme.colors.text.primary1
|
||||||
|
Text(
|
||||||
|
text = combinedReference(
|
||||||
|
stringReference("${StringsSigns.LIGHTNING} "),
|
||||||
|
resourceReference(R.string.quick_top_up_title),
|
||||||
|
).resolveReference(),
|
||||||
|
style = TangemTheme.typography.subtitle1.copy(fontWeight = FontWeight.SemiBold),
|
||||||
|
modifier = Modifier.graphicsLayer {
|
||||||
|
colorFilter = ColorFilter.tint(textColor, BlendMode.SrcIn)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6),
|
||||||
|
) {
|
||||||
|
state.amounts.fastForEach { amountUM ->
|
||||||
|
Button(
|
||||||
|
onClick = amountUM.onClick,
|
||||||
|
shape = CircleShape,
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
horizontal = TangemTheme.dimens.spacing12,
|
||||||
|
vertical = TangemTheme.dimens.spacing0,
|
||||||
|
),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = TangemTheme.colors.background.primary,
|
||||||
|
contentColor = TangemTheme.colors.text.primary1,
|
||||||
|
),
|
||||||
|
modifier = Modifier.heightIn(TangemTheme.dimens.size36),
|
||||||
|
elevation = null,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = amountUM.displayValue.resolveReference(),
|
||||||
|
style = TangemTheme.typography.subtitle1.copy(fontWeight = FontWeight.SemiBold),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
|
@Composable
|
||||||
|
private fun QuickTopUpBlock_Preview() {
|
||||||
|
TangemThemePreviewRedesign {
|
||||||
|
QuickTopUpBlock(
|
||||||
|
state = QuickTopUpBlockUM(
|
||||||
|
amounts = persistentListOf(
|
||||||
|
QuickTopUpBlockUM.QuickTopUpAmountUM(
|
||||||
|
displayValue = stringReference("\$50"),
|
||||||
|
onClick = {},
|
||||||
|
),
|
||||||
|
QuickTopUpBlockUM.QuickTopUpAmountUM(
|
||||||
|
displayValue = stringReference("\$200"),
|
||||||
|
onClick = {},
|
||||||
|
),
|
||||||
|
QuickTopUpBlockUM.QuickTopUpAmountUM(
|
||||||
|
displayValue = stringReference("\$700"),
|
||||||
|
onClick = {},
|
||||||
|
),
|
||||||
|
QuickTopUpBlockUM.QuickTopUpAmountUM(
|
||||||
|
displayValue = resourceReference(R.string.quick_top_up_chip_other),
|
||||||
|
onClick = {},
|
||||||
|
isOther = true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
modifier = Modifier.padding(TangemTheme.dimens.spacing12),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -226,6 +226,14 @@ private fun TokenDetailsBody(
|
||||||
modifier = expressTransactionModifier,
|
modifier = expressTransactionModifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
tokenDetailsUM.quickTopUpBlock?.let { quickTopUpBlock ->
|
||||||
|
item(key = "quick_top_up_block") {
|
||||||
|
QuickTopUpBlock(
|
||||||
|
state = quickTopUpBlock,
|
||||||
|
modifier = itemModifier.padding(vertical = TangemTheme.dimens2.x0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
with(txHistoryComponent) {
|
with(txHistoryComponent) {
|
||||||
txHistoryContent(listState = listState, state = txHistoryState)
|
txHistoryContent(listState = listState, state = txHistoryState)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,15 @@ internal fun TokenDetailsScreenLegacy(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.quickTopUpBlock?.let { quickTopUpBlock ->
|
||||||
|
item(key = "quick_top_up_block") {
|
||||||
|
QuickTopUpBlock(
|
||||||
|
state = quickTopUpBlock,
|
||||||
|
modifier = itemModifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
with(txHistoryComponent) {
|
with(txHistoryComponent) {
|
||||||
txHistoryContentLegacy(listState = listState, state = txHistoryComponentState)
|
txHistoryContentLegacy(listState = listState, state = txHistoryComponentState)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,347 @@
|
||||||
|
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
|
||||||
|
|
||||||
|
import arrow.core.left
|
||||||
|
import arrow.core.right
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.tangem.core.res.R
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
|
import com.tangem.domain.onramp.model.OnrampAvailability
|
||||||
|
import com.tangem.domain.onramp.model.OnrampCountry
|
||||||
|
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||||
|
import com.tangem.domain.onramp.model.error.OnrampError
|
||||||
|
import com.tangem.domain.txhistory.models.TxHistoryStateError
|
||||||
|
import com.tangem.features.tokendetails.TokenDetailsFeatureToggles
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
internal class QuickTopUpBlockFactoryTest {
|
||||||
|
|
||||||
|
private val featureToggles: TokenDetailsFeatureToggles = mockk {
|
||||||
|
every { isQuickTopUpEnabled } returns true
|
||||||
|
}
|
||||||
|
private val factory = QuickTopUpBlockFactory(featureToggles)
|
||||||
|
|
||||||
|
private val zeroBalanceStatus: CryptoCurrencyStatus = mockk {
|
||||||
|
every { value } returns mockk<CryptoCurrencyStatus.Loaded> {
|
||||||
|
every { amount } returns BigDecimal.ZERO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val nonZeroBalanceStatus: CryptoCurrencyStatus = mockk {
|
||||||
|
every { value } returns mockk<CryptoCurrencyStatus.Loaded> {
|
||||||
|
every { amount } returns BigDecimal.TEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val usdCurrency = OnrampCurrency(
|
||||||
|
name = "US Dollar",
|
||||||
|
code = "USD",
|
||||||
|
image = null,
|
||||||
|
precision = 2,
|
||||||
|
unit = "$",
|
||||||
|
)
|
||||||
|
|
||||||
|
private val eurCurrency = OnrampCurrency(
|
||||||
|
name = "Euro",
|
||||||
|
code = "EUR",
|
||||||
|
image = null,
|
||||||
|
precision = 2,
|
||||||
|
unit = "€",
|
||||||
|
)
|
||||||
|
|
||||||
|
private val gbpCurrency = OnrampCurrency(
|
||||||
|
name = "British Pound",
|
||||||
|
code = "GBP",
|
||||||
|
image = null,
|
||||||
|
precision = 2,
|
||||||
|
unit = "£",
|
||||||
|
)
|
||||||
|
|
||||||
|
private val countryMock: OnrampCountry = mockk(relaxed = true)
|
||||||
|
|
||||||
|
private val availableUsd: OnrampAvailability = OnrampAvailability.Available(
|
||||||
|
country = countryMock,
|
||||||
|
currency = usdCurrency,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val notSupported: OnrampAvailability = OnrampAvailability.NotSupported(country = countryMock)
|
||||||
|
|
||||||
|
private val emptyHistory = TxHistoryStateError.EmptyTxHistories.left()
|
||||||
|
private val histWithItems = 5.right()
|
||||||
|
private val histRightZero = 0.right()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns null when feature toggle is disabled`() {
|
||||||
|
val disabledToggles: TokenDetailsFeatureToggles = mockk {
|
||||||
|
every { isQuickTopUpEnabled } returns false
|
||||||
|
}
|
||||||
|
val disabledFactory = QuickTopUpBlockFactory(disabledToggles)
|
||||||
|
|
||||||
|
val result = disabledFactory.build(
|
||||||
|
currencyStatus = zeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = emptyHistory,
|
||||||
|
onrampAvailability = availableUsd.right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns null when balance is non-zero`() {
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = nonZeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = emptyHistory,
|
||||||
|
onrampAvailability = availableUsd.right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns null when history has transactions`() {
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = zeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = histWithItems,
|
||||||
|
onrampAvailability = availableUsd.right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns null when onramp is not available`() {
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = zeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = emptyHistory,
|
||||||
|
onrampAvailability = notSupported.right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns null when currency is not USD or EUR`() {
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = zeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = emptyHistory,
|
||||||
|
onrampAvailability = OnrampAvailability.Available(
|
||||||
|
country = countryMock,
|
||||||
|
currency = gbpCurrency,
|
||||||
|
).right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns block with USD presets when all conditions met`() {
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = zeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = emptyHistory,
|
||||||
|
onrampAvailability = availableUsd.right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull()
|
||||||
|
val amounts = result!!.amounts
|
||||||
|
assertThat(amounts.map { it.displayValue }).containsExactly(
|
||||||
|
stringReference("$50"),
|
||||||
|
stringReference("$200"),
|
||||||
|
stringReference("$700"),
|
||||||
|
resourceReference(R.string.quick_top_up_chip_other),
|
||||||
|
).inOrder()
|
||||||
|
assertThat(amounts.last().isOther).isTrue()
|
||||||
|
assertThat(amounts.take(3).all { !it.isOther }).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns block with EUR presets`() {
|
||||||
|
val availableEur = OnrampAvailability.Available(
|
||||||
|
country = countryMock,
|
||||||
|
currency = eurCurrency,
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = zeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = emptyHistory,
|
||||||
|
onrampAvailability = availableEur.right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull()
|
||||||
|
val amounts = result!!.amounts
|
||||||
|
assertThat(amounts.map { it.displayValue }).containsExactly(
|
||||||
|
stringReference("€50"),
|
||||||
|
stringReference("€200"),
|
||||||
|
stringReference("€650"),
|
||||||
|
resourceReference(R.string.quick_top_up_chip_other),
|
||||||
|
).inOrder()
|
||||||
|
assertThat(amounts.last().isOther).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns block when history count is right zero (boundary case)`() {
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = zeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = histRightZero,
|
||||||
|
onrampAvailability = availableUsd.right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns block when ConfirmResidency and country supports onramp with USD`() {
|
||||||
|
val usdCountry = OnrampCountry(
|
||||||
|
id = "us",
|
||||||
|
name = "United States",
|
||||||
|
code = "US",
|
||||||
|
image = "",
|
||||||
|
alpha3 = "USA",
|
||||||
|
continent = "America",
|
||||||
|
defaultCurrency = usdCurrency,
|
||||||
|
onrampAvailable = true,
|
||||||
|
)
|
||||||
|
val confirmResidency = OnrampAvailability.ConfirmResidency(country = usdCountry)
|
||||||
|
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = zeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = emptyHistory,
|
||||||
|
onrampAvailability = confirmResidency.right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull()
|
||||||
|
val amounts = result!!.amounts
|
||||||
|
assertThat(amounts.map { it.displayValue }).containsExactly(
|
||||||
|
stringReference("$50"),
|
||||||
|
stringReference("$200"),
|
||||||
|
stringReference("$700"),
|
||||||
|
resourceReference(R.string.quick_top_up_chip_other),
|
||||||
|
).inOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns null when onramp availability is error`() {
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = zeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = emptyHistory,
|
||||||
|
onrampAvailability = OnrampError.DataError(code = "error", description = null).left(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns null when balance is loading (amount is null)`() {
|
||||||
|
val loadingStatus: CryptoCurrencyStatus = mockk {
|
||||||
|
every { value } returns CryptoCurrencyStatus.Loading
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = loadingStatus,
|
||||||
|
isTxHistoryEmpty = emptyHistory,
|
||||||
|
onrampAvailability = availableUsd.right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns null when tx history is not implemented`() {
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = zeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = TxHistoryStateError.TxHistoryNotImplemented.left(),
|
||||||
|
onrampAvailability = availableUsd.right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns null when tx history fetch fails with data error`() {
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = zeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = TxHistoryStateError.DataError(RuntimeException("network error")).left(),
|
||||||
|
onrampAvailability = availableUsd.right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns null when ConfirmResidency with non-USD or EUR default currency`() {
|
||||||
|
val gbpCountry = OnrampCountry(
|
||||||
|
id = "gb",
|
||||||
|
name = "United Kingdom",
|
||||||
|
code = "GB",
|
||||||
|
image = "",
|
||||||
|
alpha3 = "GBR",
|
||||||
|
continent = "Europe",
|
||||||
|
defaultCurrency = gbpCurrency,
|
||||||
|
onrampAvailable = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = zeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = emptyHistory,
|
||||||
|
onrampAvailability = OnrampAvailability.ConfirmResidency(country = gbpCountry).right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns null when ConfirmResidency but country does not support onramp`() {
|
||||||
|
val restrictedCountry = OnrampCountry(
|
||||||
|
id = "kp",
|
||||||
|
name = "North Korea",
|
||||||
|
code = "KP",
|
||||||
|
image = "",
|
||||||
|
alpha3 = "PRK",
|
||||||
|
continent = "Asia",
|
||||||
|
defaultCurrency = usdCurrency,
|
||||||
|
onrampAvailable = false,
|
||||||
|
)
|
||||||
|
val confirmResidency = OnrampAvailability.ConfirmResidency(country = restrictedCountry)
|
||||||
|
|
||||||
|
val result = factory.build(
|
||||||
|
currencyStatus = zeroBalanceStatus,
|
||||||
|
isTxHistoryEmpty = emptyHistory,
|
||||||
|
onrampAvailability = confirmResidency.right(),
|
||||||
|
onPresetClick = { _, _ -> },
|
||||||
|
onOtherClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue