Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-13 12:29:40 +03:00
parent a34cb0687c
commit e65d8a0581
10 changed files with 62 additions and 47 deletions

View file

@ -1,12 +1,13 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
import com.tangem.feature.swap.domain.models.domain.SwapProvider
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.collections.immutable.ImmutableList
internal data class SwapTransactionsState(
val txId: String,
@ -14,10 +15,10 @@ internal data class SwapTransactionsState(
val txUrl: String? = null,
val timestamp: TextReference,
val fiatSymbol: String,
val activeStatus: MutableStateFlow<ExchangeStatus?>,
val hasFailed: MutableStateFlow<Boolean>,
val statuses: MutableStateFlow<List<ExchangeStatusState>>,
val notification: MutableStateFlow<ExchangeStatusNotifications?> = MutableStateFlow(null),
val activeStatus: ExchangeStatus?,
val hasFailed: Boolean,
val statuses: ImmutableList<ExchangeStatusState>,
val notification: ExchangeStatusNotifications? = null,
val toCryptoCurrencyId: CryptoCurrency.ID,
val toCryptoAmount: String,
val toCryptoSymbol: String,
@ -32,6 +33,7 @@ internal data class SwapTransactionsState(
val onGoToProviderClick: (String) -> Unit,
)
@Immutable
internal class ExchangeStatusState(
val status: ExchangeStatus,
val text: TextReference,

View file

@ -34,6 +34,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@Suppress("TooManyFunctions")
internal class TokenDetailsStateFactory(
private val currentStateProvider: Provider<TokenDetailsState>,
private val appCurrencyProvider: Provider<AppCurrency>,
@ -247,6 +248,19 @@ internal class TokenDetailsStateFactory(
)
}
fun updateStateWithExchangeStatusBottomSheet(swapTxState: SwapTransactionsState): TangemBottomSheetConfig? {
val state = currentStateProvider()
val bottomSheetConfig = state.bottomSheetConfig
val currentConfig = bottomSheetConfig?.content as? ExchangeStatusBottomSheetConfig ?: return bottomSheetConfig
return bottomSheetConfig.copy(
content = if (currentConfig.value != swapTxState) {
ExchangeStatusBottomSheetConfig(swapTxState)
} else {
currentConfig
},
)
}
fun getStateAndTriggerEvent(
state: TokenDetailsState,
errorMessage: TextReference,

View file

@ -20,11 +20,10 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
import com.tangem.features.tokendetails.impl.R
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import java.math.BigDecimal
internal class TokenDetailsSwapTransactionsStateConverter(
@ -70,14 +69,12 @@ internal class TokenDetailsSwapTransactionsStateConverter(
txUrl = transaction.status?.txUrl,
timestamp = TextReference.Str("${timestamp.toDateFormat()}, ${timestamp.toTimeFormat()}"),
fiatSymbol = appCurrency.symbol,
statuses = MutableStateFlow(getStatuses(transaction.status?.status)),
hasFailed = MutableStateFlow(transaction.status?.status == ExchangeStatus.Failed),
activeStatus = MutableStateFlow(transaction.status?.status),
notification = MutableStateFlow(
getNotification(
transaction.status?.status,
transaction.status?.txUrl,
),
statuses = getStatuses(transaction.status?.status),
hasFailed = transaction.status?.status == ExchangeStatus.Failed,
activeStatus = transaction.status?.status,
notification = getNotification(
transaction.status?.status,
transaction.status?.txUrl,
),
toCryptoCurrencyId = toCurrency.currency.id,
toCryptoAmount = BigDecimalFormatter.formatCryptoAmount(
@ -109,13 +106,16 @@ internal class TokenDetailsSwapTransactionsStateConverter(
return result.toPersistentList()
}
fun updateTxStatus(tx: SwapTransactionsState, statusModel: ExchangeStatusModel?) {
if (statusModel == null || tx.activeStatus.value == statusModel.status) return
val hasFailed = tx.hasFailed.value || statusModel.status == ExchangeStatus.Failed
tx.activeStatus.update { statusModel.status }
tx.hasFailed.update { hasFailed }
tx.notification.update { getNotification(statusModel.status, statusModel.txUrl) }
tx.statuses.update { getStatuses(statusModel.status, hasFailed) }
fun updateTxStatus(tx: SwapTransactionsState, statusModel: ExchangeStatusModel?): SwapTransactionsState {
if (statusModel == null || tx.activeStatus == statusModel.status) return tx
val hasFailed = tx.hasFailed || statusModel.status == ExchangeStatus.Failed
return tx.copy(
activeStatus = statusModel.status,
hasFailed = hasFailed,
notification = getNotification(statusModel.status, statusModel.txUrl),
statuses = getStatuses(statusModel.status, hasFailed),
txUrl = statusModel.txUrl,
)
}
private fun getFiatAmount(toFiatAmount: BigDecimal): String {
@ -149,8 +149,8 @@ internal class TokenDetailsSwapTransactionsStateConverter(
}
}
private fun getStatuses(status: ExchangeStatus?, hasFailed: Boolean = false): List<ExchangeStatusState> {
if (status == null) return emptyList()
private fun getStatuses(status: ExchangeStatus?, hasFailed: Boolean = false): ImmutableList<ExchangeStatusState> {
if (status == null) return persistentListOf()
val isWaiting = status == ExchangeStatus.New || status == ExchangeStatus.Waiting
val isConfirming = status == ExchangeStatus.Confirming
val isVerifying = status == ExchangeStatus.Verifying
@ -181,7 +181,7 @@ internal class TokenDetailsSwapTransactionsStateConverter(
isRefunded = isRefunded,
hasFailed = hasFailed,
),
)
).toPersistentList()
}
private fun waitStep(isNew: Boolean, isNewDone: Boolean) = ExchangeStatusState(

View file

@ -18,23 +18,21 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
import com.tangem.feature.tokendetails.presentation.tokendetails.state.ExchangeStatusState
import com.tangem.features.tokendetails.impl.R
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.collections.immutable.ImmutableList
@Composable
internal fun ExchangeStatusBlock(
statuses: MutableStateFlow<List<ExchangeStatusState>>,
statuses: ImmutableList<ExchangeStatusState>,
showLink: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val statusValues = statuses.collectAsStateWithLifecycle()
Column(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
@ -77,10 +75,10 @@ internal fun ExchangeStatusBlock(
}
}
statusValues.value.forEachIndexed { index, item ->
statuses.forEachIndexed { index, item ->
ExchangeStatusStep(
stepStatus = item,
isLast = index == statusValues.value.lastIndex,
isLast = index == statuses.lastIndex,
)
}
}

View file

@ -8,7 +8,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH10
import com.tangem.core.ui.components.SpacerH12
@ -36,8 +35,6 @@ internal fun ExchangeStatusBottomSheet(config: TangemBottomSheetConfig) {
@Composable
private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetConfig) {
val config = content.value
val status = config.activeStatus.collectAsStateWithLifecycle()
val notification = config.notification.collectAsStateWithLifecycle()
Column(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
) {
@ -77,15 +74,15 @@ private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetC
SpacerH12()
ExchangeStatusBlock(
statuses = config.statuses,
showLink = notification.value == null && config.txUrl != null,
showLink = config.notification == null && config.txUrl != null,
onClick = { config.onGoToProviderClick(config.txUrl.orEmpty()) },
)
AnimatedContent(
targetState = notification.value,
targetState = config.notification,
label = "Exchange Status Notification Change",
) {
it?.let {
val tint = when (status.value) {
val tint = when (config.activeStatus) {
ExchangeStatus.Verifying -> TangemTheme.colors.icon.attention
ExchangeStatus.Failed -> TangemTheme.colors.icon.warning
else -> null

View file

@ -18,7 +18,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.constraintlayout.compose.*
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
@ -41,8 +40,7 @@ internal fun LazyListScope.swapTransactionsItems(
contentType = { swapTxs[it]::class.java },
) {
val item = swapTxs[it]
val status = item.activeStatus.collectAsStateWithLifecycle()
val (iconRes, tint) = when (status.value) {
val (iconRes, tint) = when (item.activeStatus) {
ExchangeStatus.Verifying -> R.drawable.ic_alert_triangle_20 to TangemTheme.colors.icon.attention
ExchangeStatus.Failed -> R.drawable.ic_alert_circle_24 to TangemTheme.colors.icon.warning
else -> null to null

View file

@ -12,8 +12,8 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.models.analytics.TokenExchangeAnalyticsEvent
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.SwapTransactionRepository
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel
import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel
@ -81,8 +81,9 @@ internal class ExchangeStatusFactory(
swapTxList.map { tx ->
async {
val statusModel = getExchangeStatus(tx.txId)
swapTransactionsStateConverter.updateTxStatus(tx, statusModel)
tx.removeIfFinished(statusModel?.status)
swapTransactionsStateConverter
.updateTxStatus(tx, statusModel)
.removeIfFinished(statusModel?.status)
}
}
.awaitAll()

View file

@ -43,6 +43,7 @@ import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRout
import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTransactionsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
import com.tangem.features.tokendetails.impl.R
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.utils.coroutines.*
@ -229,7 +230,14 @@ internal class TokenDetailsViewModel @Inject constructor(
}
},
onSuccess = { updatedTxs ->
uiState = uiState.copy(swapTxs = updatedTxs)
val config = uiState.bottomSheetConfig?.content as? ExchangeStatusBottomSheetConfig
val currentTx = updatedTxs.firstOrNull { it.txId == config?.value?.txId }
uiState = uiState.copy(
swapTxs = updatedTxs,
bottomSheetConfig = currentTx?.let(
stateFactory::updateStateWithExchangeStatusBottomSheet,
),
)
},
onError = {},
),

View file

@ -63,6 +63,4 @@ sealed class WalletScreenAnalyticsEvent {
object NoticeWalletLocked : MainScreen(event = "Notice - Wallet Locked")
object WalletUnlockTapped : MainScreen(event = "Button - Wallet Unlock Tapped")
}
object SwapPromo : AnalyticsEvent(category = "Swap Promo", event = "Button - Close")
}

View file

@ -779,7 +779,6 @@ internal class WalletViewModel @Inject constructor(
override fun onCloseSwapPromoNotificationClick() {
viewModelScope.launch(dispatchers.main) {
shouldShowSwapPromoWalletUseCase.neverToShow()
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.SwapPromo)
}
}