Updated on 2026-08-14
This commit is contained in:
parent
1485cebb6b
commit
ac3676ca4b
11 changed files with 205 additions and 9 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ internal object TokenDetailsPreviewData {
|
|||
tokenInfoBlockState = tokenInfoBlockState,
|
||||
tokenBalanceBlockState = balanceLoading,
|
||||
marketPriceBlockState = marketPriceLoading,
|
||||
notifications = persistentListOf(),
|
||||
txHistoryState = TxHistoryState.Content(
|
||||
contentItems = MutableStateFlow(
|
||||
value = TxHistoryState.getDefaultLoadingTransactions {},
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ internal class TokenDetailsSkeletonStateConverter(
|
|||
actionButtons = createButtons(),
|
||||
),
|
||||
marketPriceBlockState = MarketPriceBlockState.Loading(value.cryptoCurrency.name),
|
||||
notifications = persistentListOf(),
|
||||
pendingTxs = persistentListOf(),
|
||||
txHistoryState = TxHistoryState.Content(
|
||||
contentItems = MutableStateFlow(
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -27,4 +27,8 @@ interface TokenDetailsClickIntents {
|
|||
fun onExploreClick()
|
||||
|
||||
fun onDismissBottomSheet()
|
||||
|
||||
fun onCloseRentInfoNotification()
|
||||
|
||||
fun onCloseExistentialDepositNotification()
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue