Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-19 10:23:57 +05:00
parent 1485cebb6b
commit ac3676ca4b
11 changed files with 205 additions and 9 deletions

View file

@ -8,7 +8,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.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
@ -22,15 +21,18 @@ class GetCurrencyWarningsUseCase(
private val dispatchers: CoroutineDispatcherProvider,
) {
suspend operator fun invoke(userWallet: UserWallet, currency: CryptoCurrency): Flow<Set<CryptoCurrencyWarning>> {
suspend operator fun invoke(
userWalletId: UserWalletId,
currency: CryptoCurrency,
): Flow<Set<CryptoCurrencyWarning>> {
return combine(
getFeeWarningFlow(
userWalletId = userWallet.walletId,
userWalletId = userWalletId,
networkId = currency.network.id,
currencyId = currency.id,
),
flowOf(walletManagersFacade.getRentInfo(userWallet.walletId, currency.network)),
flowOf(walletManagersFacade.getExistentialDeposit(userWallet.walletId, currency.network)),
flowOf(walletManagersFacade.getRentInfo(userWalletId, currency.network)),
flowOf(walletManagersFacade.getExistentialDeposit(userWalletId, currency.network)),
) { maybeFeeWarning, maybeRentWarning, maybeEdWarning ->
setOfNotNull(
maybeRentWarning,

View file

@ -83,6 +83,7 @@ internal object TokenDetailsPreviewData {
tokenInfoBlockState = tokenInfoBlockState,
tokenBalanceBlockState = balanceLoading,
marketPriceBlockState = marketPriceLoading,
notifications = persistentListOf(),
txHistoryState = TxHistoryState.Content(
contentItems = MutableStateFlow(
value = TxHistoryState.getDefaultLoadingTransactions {},

View file

@ -5,7 +5,9 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsDialogConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsPullToRefreshConfig
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentList
internal data class TokenDetailsState(
@ -13,6 +15,7 @@ internal data class TokenDetailsState(
val tokenInfoBlockState: TokenInfoBlockState,
val tokenBalanceBlockState: TokenDetailsBalanceBlockState,
val marketPriceBlockState: MarketPriceBlockState,
val notifications: ImmutableList<TokenDetailsNotification>,
val pendingTxs: PersistentList<TransactionState>,
val txHistoryState: TxHistoryState,
val dialogConfig: TokenDetailsDialogConfig?,

View file

@ -0,0 +1,79 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.components
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.networkIconResId
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.tokens.models.warnings.CryptoCurrencyWarning
import com.tangem.features.tokendetails.impl.R
// TODO: Finalize notification strings [REDACTED_JIRA]
@Immutable
sealed class TokenDetailsNotification(open val config: NotificationConfig) {
data class RentInfoNotification(
private val rentInfo: CryptoCurrencyWarning.Rent,
private val onCloseClick: () -> Unit,
) : TokenDetailsNotification(
config = NotificationConfig(
title = TextReference.Res(R.string.send_network_fee_title),
subtitle = TextReference.Res(
id = R.string.solana_rent_warning,
formatArgs = wrappedList(rentInfo.rent, rentInfo.exemptionAmount),
),
iconResId = R.drawable.img_attention_20,
onCloseClick = onCloseClick,
),
)
data class ExistentialDepositNotification(
private val existentialInfo: CryptoCurrencyWarning.ExistentialDeposit,
private val onCloseClick: () -> Unit,
) : TokenDetailsNotification(
config = NotificationConfig(
title = TextReference.Str("Existential Deposit"),
subtitle = TextReference.Res(
id = R.string.warning_existential_deposit_message,
formatArgs = wrappedList(existentialInfo.currencyName, existentialInfo.edStringValueWithSymbol),
),
iconResId = R.drawable.img_attention_20,
onCloseClick = onCloseClick,
),
)
data class NetworkFeeFeeNotification(
private val feeInfo: CryptoCurrencyWarning.BalanceNotEnoughForFee,
private val onBuyClick: () -> Unit,
) : TokenDetailsNotification(
config = NotificationConfig(
title = TextReference.Res(
id = R.string.notification_title_not_enough_funds,
formatArgs = wrappedList(feeInfo.blockchainFullName),
),
subtitle = TextReference.Res(
id = R.string.token_details_send_blocked_fee_format,
formatArgs = wrappedList(
feeInfo.currency.name,
feeInfo.blockchainFullName,
feeInfo.currency.name,
feeInfo.blockchainFullName,
feeInfo.blockchainSymbol,
),
),
iconResId = feeInfo.currency.networkIconResId,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = TextReference.Str("Buy"),
onClick = onBuyClick,
),
),
)
object NetworksUnreachableNotification : TokenDetailsNotification(
config = NotificationConfig(
title = TextReference.Str("Some networks are unreachable"),
subtitle = TextReference.Str("The problem is on the crypto-network side. It will be fixed soon."),
iconResId = R.drawable.img_attention_20,
),
)
}

View file

@ -10,8 +10,10 @@ import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState
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.factory.txhistory.TokenDetailsTxHistoryToTransactionStateConverter
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import java.math.BigDecimal
@ -32,8 +34,12 @@ internal class TokenDetailsLoadedBalanceConverter(
}
private fun convertError(): TokenDetailsState {
// TODO: [REDACTED_JIRA]
return currentStateProvider()
val state = currentStateProvider()
return state.copy(
tokenBalanceBlockState = TokenDetailsBalanceBlockState.Error(state.tokenBalanceBlockState.actionButtons),
marketPriceBlockState = MarketPriceBlockState.Error(state.marketPriceBlockState.currencyName),
notifications = persistentListOf(TokenDetailsNotification.NetworksUnreachableNotification),
)
}
private fun convert(status: CryptoCurrencyStatus): TokenDetailsState {
@ -68,7 +74,6 @@ internal class TokenDetailsLoadedBalanceConverter(
is CryptoCurrencyStatus.NoAccount,
is CryptoCurrencyStatus.Custom,
is CryptoCurrencyStatus.NoAmount,
// TODO: [REDACTED_JIRA]
is CryptoCurrencyStatus.Unreachable,
-> {
TokenDetailsBalanceBlockState.Error(currentState.actionButtons)

View file

@ -0,0 +1,49 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
import com.tangem.domain.tokens.models.warnings.CryptoCurrencyWarning
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.viewmodels.TokenDetailsClickIntents
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.removeBy
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
internal class TokenDetailsNotificationConverter(
private val clickIntents: TokenDetailsClickIntents,
) : Converter<Set<CryptoCurrencyWarning>, ImmutableList<TokenDetailsNotification>> {
override fun convert(value: Set<CryptoCurrencyWarning>): ImmutableList<TokenDetailsNotification> {
return value.map(::mapToNotification).toImmutableList()
}
fun removeExistentialDeposit(currentState: TokenDetailsState): ImmutableList<TokenDetailsNotification> {
val newNotifications = currentState.notifications.toMutableList()
newNotifications.removeBy { it is TokenDetailsNotification.ExistentialDepositNotification }
return newNotifications.toImmutableList()
}
fun removeRentInfo(currentState: TokenDetailsState): ImmutableList<TokenDetailsNotification> {
val newNotifications = currentState.notifications.toMutableList()
newNotifications.removeBy { it is TokenDetailsNotification.RentInfoNotification }
return newNotifications.toImmutableList()
}
private fun mapToNotification(warning: CryptoCurrencyWarning): TokenDetailsNotification {
return when (warning) {
is CryptoCurrencyWarning.BalanceNotEnoughForFee -> TokenDetailsNotification.NetworkFeeFeeNotification(
feeInfo = warning,
onBuyClick = clickIntents::onBuyClick,
)
is CryptoCurrencyWarning.ExistentialDeposit -> TokenDetailsNotification.ExistentialDepositNotification(
existentialInfo = warning,
onCloseClick = clickIntents::onCloseExistentialDepositNotification,
)
is CryptoCurrencyWarning.Rent -> TokenDetailsNotification.RentInfoNotification(
rentInfo = warning,
onCloseClick = clickIntents::onCloseRentInfoNotification,
)
CryptoCurrencyWarning.SomeNetworksUnreachable -> TokenDetailsNotification.NetworksUnreachableNotification
}
}
}

View file

@ -43,6 +43,7 @@ internal class TokenDetailsSkeletonStateConverter(
actionButtons = createButtons(),
),
marketPriceBlockState = MarketPriceBlockState.Loading(value.cryptoCurrency.name),
notifications = persistentListOf(),
pendingTxs = persistentListOf(),
txHistoryState = TxHistoryState.Content(
contentItems = MutableStateFlow(

View file

@ -12,6 +12,7 @@ import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.tokens.models.warnings.CryptoCurrencyWarning
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryListError
import com.tangem.domain.txhistory.models.TxHistoryStateError
@ -36,6 +37,10 @@ internal class TokenDetailsStateFactory(
TokenDetailsSkeletonStateConverter(clickIntents = clickIntents)
}
private val notificationConverter by lazy {
TokenDetailsNotificationConverter(clickIntents = clickIntents)
}
private val tokenDetailsLoadedBalanceConverter by lazy {
TokenDetailsLoadedBalanceConverter(
currentStateProvider = currentStateProvider,
@ -177,4 +182,19 @@ internal class TokenDetailsStateFactory(
)
} ?: return currentState
}
fun getStateWithNotifications(warnings: Set<CryptoCurrencyWarning>): TokenDetailsState {
val state = currentStateProvider()
return state.copy(notifications = notificationConverter.convert(warnings))
}
fun getStateWithRemovedExistentialNotification(): TokenDetailsState {
val state = currentStateProvider()
return state.copy(notifications = notificationConverter.removeExistentialDeposit(state))
}
fun getStateWithRemovedRentNotification(): TokenDetailsState {
val state = currentStateProvider()
return state.copy(notifications = notificationConverter.removeRentInfo(state))
}
}

View file

@ -1,8 +1,10 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
@ -18,6 +20,7 @@ import androidx.paging.compose.collectAsLazyPagingItems
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheet
import com.tangem.core.ui.components.marketprice.MarketPriceBlock
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.transactions.Transaction
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
@ -31,7 +34,8 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.T
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock
import kotlinx.collections.immutable.PersistentList
@OptIn(ExperimentalMaterialApi::class)
@Suppress("LongMethod")
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
@Composable
internal fun TokenDetailsScreen(state: TokenDetailsState) {
Scaffold(
@ -72,6 +76,12 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
)
}
item { TokenDetailsBalanceBlock(modifier = itemModifier, state = state.tokenBalanceBlockState) }
items(
items = state.notifications,
key = { it.config.title.hashCode() },
contentType = { it.config::class.java },
itemContent = { Notification(config = it.config, modifier = itemModifier.animateItemPlacement()) },
)
item(
key = MarketPriceBlockState::class.java,
contentType = MarketPriceBlockState::class.java,

View file

@ -27,4 +27,8 @@ interface TokenDetailsClickIntents {
fun onExploreClick()
fun onDismissBottomSheet()
fun onCloseRentInfoNotification()
fun onCloseExistentialDepositNotification()
}

View file

@ -53,6 +53,7 @@ internal class TokenDetailsViewModel @Inject constructor(
private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase,
private val isBalanceHiddenUseCase: IsBalanceHiddenUseCase,
private val listenToFlipsUseCase: ListenToFlipsUseCase,
private val getCurrencyWarningsUseCase: GetCurrencyWarningsUseCase,
private val walletManagersFacade: WalletManagersFacade,
private val reduxStateHolder: ReduxStateHolder,
savedStateHandle: SavedStateHandle,
@ -100,6 +101,7 @@ internal class TokenDetailsViewModel @Inject constructor(
private fun updateContent(selectedWallet: UserWallet) {
updateMarketPrice(selectedWallet = selectedWallet)
updateTxHistory()
updateWarnings(selectedWallet = selectedWallet)
}
private fun handleBalanceHiding(owner: LifecycleOwner) {
@ -126,6 +128,18 @@ internal class TokenDetailsViewModel @Inject constructor(
.launchIn(viewModelScope)
}
private fun updateWarnings(selectedWallet: UserWallet) {
viewModelScope.launch {
getCurrencyWarningsUseCase.invoke(
userWalletId = selectedWallet.walletId,
currency = cryptoCurrency,
)
.distinctUntilChanged()
.onEach { uiState = stateFactory.getStateWithNotifications(it) }
.launchIn(viewModelScope)
}
}
private fun updateMarketPrice(selectedWallet: UserWallet) {
getCurrencyStatusUpdatesUseCase(
userWalletId = selectedWallet.walletId,
@ -315,4 +329,12 @@ internal class TokenDetailsViewModel @Inject constructor(
override fun onDismissBottomSheet() {
uiState = stateFactory.getStateWithClosedBottomSheet()
}
override fun onCloseExistentialDepositNotification() {
uiState = stateFactory.getStateWithRemovedExistentialNotification()
}
override fun onCloseRentInfoNotification() {
uiState = stateFactory.getStateWithRemovedRentNotification()
}
}