Updated on 2026-08-14
This commit is contained in:
parent
cf182832db
commit
ae979f60cd
11 changed files with 110 additions and 65 deletions
|
|
@ -237,7 +237,7 @@ object OnboardingHelper {
|
|||
|
||||
scope.launch {
|
||||
val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase)
|
||||
val isRussia = getUserCountryCodeUseCase().isRight { it is UserCountry.Russia }
|
||||
val isRussia = getUserCountryCodeUseCase.invokeSync().isRight { it is UserCountry.Russia }
|
||||
|
||||
val onrampFeatureToggles = store.inject(DaggerGraphState::onrampFeatureToggles)
|
||||
if (isRussia && !onrampFeatureToggles.isFeatureEnabled) {
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ object TradeCryptoMiddleware {
|
|||
val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase)
|
||||
val onrampFeatureToggles = store.inject(DaggerGraphState::onrampFeatureToggles)
|
||||
|
||||
val isRussia = getUserCountryCodeUseCase().isRight { it is UserCountry.Russia }
|
||||
val isRussia = getUserCountryCodeUseCase.invokeSync().isRight { it is UserCountry.Russia }
|
||||
|
||||
if (action.checkUserLocation && isRussia && !onrampFeatureToggles.isFeatureEnabled) {
|
||||
val dialogData = topUrl?.let {
|
||||
|
|
|
|||
|
|
@ -245,6 +245,16 @@ sealed class NotificationUM(val config: NotificationConfig) {
|
|||
title = resourceReference(id = R.string.action_buttons_swap_no_available_pair_notification_title),
|
||||
subtitle = resourceReference(id = R.string.action_buttons_swap_no_available_pair_notification_message),
|
||||
)
|
||||
|
||||
data object SellingRegionalRestriction : Warning(
|
||||
title = resourceReference(id = R.string.selling_regional_restriction_alert_title),
|
||||
subtitle = resourceReference(id = R.string.selling_regional_restriction_alert_message),
|
||||
)
|
||||
|
||||
data object InsufficientBalanceForSelling : Warning(
|
||||
title = resourceReference(id = R.string.selling_insufficient_balance_alert_title),
|
||||
subtitle = resourceReference(id = R.string.selling_insufficient_balance_alert_message),
|
||||
)
|
||||
}
|
||||
|
||||
open class Info(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.domain.settings.usercountry.models.UserCountry
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
|
@ -127,6 +128,8 @@ internal class DefaultSettingsRepository(
|
|||
return null
|
||||
}
|
||||
|
||||
override fun getUserCountryCode(): StateFlow<UserCountry?> = userCountryFlow
|
||||
|
||||
override suspend fun fetchUserCountryCode() {
|
||||
Timber.i("Start fetching user country code")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.domain.settings.repositories
|
||||
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface SettingsRepository {
|
||||
|
|
@ -39,6 +40,8 @@ interface SettingsRepository {
|
|||
|
||||
suspend fun getUserCountryCodeSync(): UserCountry?
|
||||
|
||||
fun getUserCountryCode(): StateFlow<UserCountry?>
|
||||
|
||||
suspend fun fetchUserCountryCode()
|
||||
|
||||
suspend fun setGoogleServicesAvailability(value: Boolean)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import arrow.core.raise.ensureNotNull
|
|||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountryError
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
* Get user country code use case
|
||||
|
|
@ -19,7 +21,17 @@ class GetUserCountryUseCase(
|
|||
private val settingsRepository: SettingsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Either<UserCountryError, UserCountry> {
|
||||
operator fun invoke(): Flow<Either<UserCountryError, UserCountry>> {
|
||||
return settingsRepository.getUserCountryCode().map { userCountryCode ->
|
||||
either {
|
||||
ensureNotNull(userCountryCode) { UserCountryError.NotSetup }
|
||||
|
||||
userCountryCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun invokeSync(): Either<UserCountryError, UserCountry> {
|
||||
return either {
|
||||
val userCountryCode = catch(
|
||||
block = { settingsRepository.getUserCountryCodeSync() },
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.onramp.tokenlist.entity.transformer
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.tokens.TokenItemStateConverter
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
|
|
@ -19,6 +20,7 @@ internal class UpdateTokenItemsTransformer(
|
|||
private val statuses: Map<Boolean, List<CryptoCurrencyStatus>>,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val unavailableTokensHeaderReference: TextReference,
|
||||
private val warning: NotificationUM? = null,
|
||||
) : TokenListUMTransformer {
|
||||
|
||||
override fun transform(prevState: TokenListUM): TokenListUM {
|
||||
|
|
@ -38,7 +40,7 @@ internal class UpdateTokenItemsTransformer(
|
|||
),
|
||||
unavailableItems = unavailableItems.addHeader(textReference = unavailableTokensHeaderReference),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
warning = null,
|
||||
warning = warning,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
package com.tangem.features.onramp.tokenlist.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.utils.getOrElse
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
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.TokenList
|
||||
import com.tangem.domain.tokens.model.TotalFiatBalance
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
|
||||
|
|
@ -25,6 +31,7 @@ import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
|
|||
import com.tangem.features.onramp.utils.UpdateSearchBarCallbacksTransformer
|
||||
import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
|
@ -43,6 +50,7 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
private val getTokenListUseCase: GetTokenListUseCase,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
private val rampStateManager: RampStateManager,
|
||||
private val getUserCountryUseCase: GetUserCountryUseCase,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<TokenListUM> = tokenListUMController.state
|
||||
|
|
@ -69,7 +77,8 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
flow2 = getSelectedAppCurrencyUseCase().map { it.getOrElse { AppCurrency.Default } }.distinctUntilChanged(),
|
||||
flow3 = getBalanceHidingSettingsUseCase().map { it.isBalanceHidden }.distinctUntilChanged(),
|
||||
flow4 = searchManager.query,
|
||||
) { maybeTokenList, appCurrency, isBalanceHidden, query ->
|
||||
flow5 = hasRestrictionForSellFlow(),
|
||||
) { maybeTokenList, appCurrency, isBalanceHidden, query, hasRestrictionForSell ->
|
||||
val currencies = maybeTokenList.getOrElse(
|
||||
ifLoading = { it ?: TokenList.Empty },
|
||||
ifError = { TokenList.Empty },
|
||||
|
|
@ -90,16 +99,29 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
.let(::resourceReference),
|
||||
)
|
||||
} else {
|
||||
val isInsufficientBalanceForSell = if (params.filterOperation == OnrampOperation.SELL) {
|
||||
maybeTokenList.isInsufficientBalanceForSell()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
UpdateTokenItemsTransformer(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = params.onTokenClick,
|
||||
statuses = filterByQueryTokenList.filterByAvailability(),
|
||||
statuses = filterByQueryTokenList.let {
|
||||
if (hasRestrictionForSell || isInsufficientBalanceForSell) {
|
||||
mapOf(false to it)
|
||||
} else {
|
||||
it.filterByAvailability()
|
||||
}
|
||||
},
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
unavailableTokensHeaderReference = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> R.string.tokens_list_unavailable_to_purchase_header
|
||||
OnrampOperation.SELL -> R.string.tokens_list_unavailable_to_sell_header
|
||||
OnrampOperation.SWAP -> R.string.tokens_list_unavailable_to_swap_source_header
|
||||
}.let(::resourceReference),
|
||||
unavailableTokensHeaderReference = getUnavailableTokensHeaderReference(),
|
||||
warning = when {
|
||||
hasRestrictionForSell -> NotificationUM.Warning.SellingRegionalRestriction
|
||||
isInsufficientBalanceForSell -> NotificationUM.Warning.InsufficientBalanceForSelling
|
||||
else -> null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -108,6 +130,32 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun hasRestrictionForSellFlow(): Flow<Boolean> {
|
||||
return if (params.filterOperation == OnrampOperation.SELL) {
|
||||
getUserCountryUseCase().map { maybe ->
|
||||
maybe.isRight { country -> country is UserCountry.Russia }
|
||||
}
|
||||
} else {
|
||||
flowOf(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Lce<TokenListError, TokenList>.isInsufficientBalanceForSell(): Boolean {
|
||||
return if (params.filterOperation == OnrampOperation.SELL) {
|
||||
isContent {
|
||||
(it.totalFiatBalance as? TotalFiatBalance.Loaded)?.amount?.isZero() ?: false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUnavailableTokensHeaderReference() = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> R.string.tokens_list_unavailable_to_purchase_header
|
||||
OnrampOperation.SELL -> R.string.tokens_list_unavailable_to_sell_header
|
||||
OnrampOperation.SWAP -> R.string.tokens_list_unavailable_to_swap_source_header
|
||||
}.let(::resourceReference)
|
||||
|
||||
private fun onSearchQueryChange(newQuery: String) {
|
||||
val searchBar = state.value.searchBarUM
|
||||
if (searchBar.query == newQuery) return
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.onramp.tokenlist.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -42,14 +43,18 @@ internal fun TokenList(state: TokenListUM, modifier: Modifier = Modifier) {
|
|||
if (state.warning == null) {
|
||||
SearchBar(searchBarUM = state.searchBarUM)
|
||||
} else {
|
||||
when (state.warning) {
|
||||
is NotificationUM.Warning.OnrampErrorNotification -> {
|
||||
Notification(config = state.warning.config, containerColor = TangemTheme.colors.background.primary)
|
||||
AnimatedContent(targetState = state.warning, label = "") { warning ->
|
||||
when (warning) {
|
||||
is NotificationUM.Warning.OnrampErrorNotification -> {
|
||||
Notification(
|
||||
config = warning.config,
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
Notification(config = warning.config, containerColor = TangemTheme.colors.button.disabled)
|
||||
}
|
||||
}
|
||||
is NotificationUM.Warning.SwapNoAvailablePair -> {
|
||||
Notification(config = state.warning.config, containerColor = TangemTheme.colors.button.disabled)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,22 +98,6 @@ internal sealed interface WalletAlertState {
|
|||
override val onConfirmClick: (() -> Unit)? = null
|
||||
}
|
||||
|
||||
data object SellingRegionalRestriction : Basic() {
|
||||
override val title: TextReference = resourceReference(id = R.string.selling_regional_restriction_alert_title)
|
||||
override val message: TextReference =
|
||||
resourceReference(id = R.string.selling_regional_restriction_alert_message)
|
||||
|
||||
override val onConfirmClick: (() -> Unit)? = null
|
||||
}
|
||||
|
||||
data object InsufficientBalanceForSelling : Basic() {
|
||||
override val title: TextReference = resourceReference(id = R.string.selling_insufficient_balance_alert_title)
|
||||
override val message: TextReference =
|
||||
resourceReference(id = R.string.selling_insufficient_balance_alert_message)
|
||||
|
||||
override val onConfirmClick: (() -> Unit)? = null
|
||||
}
|
||||
|
||||
data object InsufficientTokensCountForSwapping : Basic() {
|
||||
override val title: TextReference =
|
||||
resourceReference(id = R.string.action_buttons_swap_no_tokens_added_alert_title)
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@ import com.tangem.domain.exchange.RampStateManager
|
|||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.IsCryptoCurrencyCoinCouldHideUseCase
|
||||
|
|
@ -46,7 +44,10 @@ import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
|||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||
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.state.model.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
|
|
@ -117,7 +118,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
private val shareManager: ShareManager,
|
||||
private val appRouter: AppRouter,
|
||||
private val rampStateManager: RampStateManager,
|
||||
private val getUserCountryUseCase: GetUserCountryUseCase,
|
||||
private val onrampFeatureToggles: OnrampFeatureToggles,
|
||||
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
|
||||
|
||||
|
|
@ -410,33 +410,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onMultiWalletSellClick(userWalletId: UserWalletId) {
|
||||
viewModelScope.launch {
|
||||
val userCountry = getUserCountryUseCase().getOrNull()
|
||||
if (userCountry is UserCountry.Russia) {
|
||||
handleError(
|
||||
alertState = WalletAlertState.SellingRegionalRestriction,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSell,
|
||||
)
|
||||
|
||||
return@launch
|
||||
}
|
||||
|
||||
val selectedWallet = stateHolder.getSelectedWallet().walletCardState as? WalletCardState.Content
|
||||
if (selectedWallet?.isZeroBalance == true) {
|
||||
handleError(
|
||||
alertState = WalletAlertState.InsufficientBalanceForSelling,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSell,
|
||||
)
|
||||
|
||||
return@launch
|
||||
}
|
||||
|
||||
onMultiWalletActionClick(
|
||||
statusFlow = rampStateManager.getSellInitializationStatus(),
|
||||
route = AppRoute.SellCrypto(userWalletId = userWalletId),
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSell,
|
||||
)
|
||||
}
|
||||
onMultiWalletActionClick(
|
||||
statusFlow = rampStateManager.getSellInitializationStatus(),
|
||||
route = AppRoute.SellCrypto(userWalletId = userWalletId),
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSell,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onMultiWalletSwapClick(userWalletId: UserWalletId) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue