Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-30 15:51:25 +03:00
parent 82c19782eb
commit f3aa08093d
21 changed files with 657 additions and 129 deletions

View file

@ -201,7 +201,7 @@
<string name="express_exchange_notification_failed_text">Visit providers website to see why</string>
<string name="express_exchange_notification_failed_title">Operation failed by provider</string>
<string name="express_exchange_notification_verification_text">Visit providers website for verification</string>
<string name="express_exchange_notification_verification_title">Provider: Verification needed</string>
<string name="express_exchange_notification_verification_title">KYC verification required by provider</string>
<string name="express_exchange_status_confirmed">Confirmed</string>
<string name="express_exchange_status_confirming">Confirming</string>
<string name="express_exchange_status_exchanged">Exchanged</string>

View file

@ -10,6 +10,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
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
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
@ -39,6 +40,8 @@ fun InputRowApprox(
rightTitle: TextReference,
rightSubtitle: TextReference,
modifier: Modifier = Modifier,
leftTitleEllipsisOffset: Int = 0,
rightTitleEllipsisOffset: Int = 0,
showDivider: Boolean = false,
) {
DividerContainer(
@ -47,7 +50,6 @@ fun InputRowApprox(
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.padding(TangemTheme.dimens.spacing12)
.fillMaxWidth(),
@ -56,6 +58,7 @@ fun InputRowApprox(
iconState = leftIcon,
title = leftTitle,
subtitle = leftSubtitle,
titleEllipsisOffset = leftTitleEllipsisOffset,
modifier = Modifier.weight(1f),
)
Icon(
@ -64,7 +67,7 @@ fun InputRowApprox(
tint = TangemTheme.colors.text.tertiary,
modifier = Modifier
.padding(
horizontal = TangemTheme.dimens.spacing8,
horizontal = TangemTheme.dimens.spacing4,
vertical = TangemTheme.dimens.spacing10,
),
)
@ -72,6 +75,7 @@ fun InputRowApprox(
iconState = rightIcon,
title = rightTitle,
subtitle = rightSubtitle,
titleEllipsisOffset = rightTitleEllipsisOffset,
modifier = Modifier.weight(1f),
)
}
@ -84,6 +88,7 @@ private fun InputRowApproxItem(
title: TextReference,
subtitle: TextReference,
modifier: Modifier = Modifier,
titleEllipsisOffset: Int = 0,
) {
Row(
modifier = modifier,
@ -103,6 +108,7 @@ private fun InputRowApproxItem(
text = title.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
ellipsis = TextEllipsis.OffsetEnd(titleEllipsisOffset),
)
EllipsisText(
text = subtitle.resolveReference(),
@ -122,11 +128,13 @@ private fun InputRowApproxPreview_Light() {
TangemTheme {
InputRowApprox(
leftIcon = TokenIconState.Loading,
leftTitle = TextReference.Str("Left title"),
leftSubtitle = TextReference.Str("Left subtitle"),
leftTitle = TextReference.Str("Left title USD"),
leftSubtitle = TextReference.Str("Left subtitle USD"),
leftTitleEllipsisOffset = 3,
rightIcon = TokenIconState.Loading,
rightTitle = TextReference.Str("Right title Right title Right title Right title Right title"),
rightSubtitle = TextReference.Str("Right subtitle"),
rightTitle = TextReference.Str("Right title Right title Right title Right title Right title USD"),
rightSubtitle = TextReference.Str("Right subtitle Right subtitle Right subtitle USD"),
rightTitleEllipsisOffset = 3,
modifier = Modifier
.background(TangemTheme.colors.background.action),
)
@ -139,11 +147,13 @@ private fun InputRowApproxPreview_Dark() {
TangemTheme(isDark = true) {
InputRowApprox(
leftIcon = TokenIconState.Loading,
leftTitle = TextReference.Str("Left title Left title Left title Left title Left title"),
leftSubtitle = TextReference.Str("Left subtitle"),
leftTitle = TextReference.Str("Left title Left title Left title Left title Left title USD"),
leftSubtitle = TextReference.Str("Left subtitle Left subtitle Left subtitle USD"),
leftTitleEllipsisOffset = 3,
rightIcon = TokenIconState.Loading,
rightTitle = TextReference.Str("Right title"),
rightSubtitle = TextReference.Str("Right subtitle"),
rightTitle = TextReference.Str("Right title USD"),
rightSubtitle = TextReference.Str("Right subtitle USD"),
rightTitleEllipsisOffset = 3,
modifier = Modifier
.background(TangemTheme.colors.background.action),
)

View file

@ -104,6 +104,16 @@ fun combinedReference(refs: WrappedList<TextReference>): TextReference {
return TextReference.Combined(refs)
}
/**
* Combines multiple [TextReference] instances into a single [TextReference].
*
* @param refs Vararg of [TextReference] instances to be combined.
* @return A [TextReference] representing the combined text references.
*/
fun combinedReference(vararg refs: TextReference): TextReference {
return TextReference.Combined(WrappedList(listOf(*refs)))
}
/** Resolve [TextReference] as [String] */
@Composable
@ReadOnlyComposable

View file

@ -1,4 +1,4 @@
package com.tangem.feature.swap.viewmodels
package com.tangem.utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay

View file

@ -43,6 +43,7 @@ internal class SwapInteractorImpl @Inject constructor(
private val transactionManager: TransactionManager,
private val userWalletManager: UserWalletManager,
private val repository: SwapRepository,
private val swapTransactionRepository: SwapTransactionRepository,
private val allowPermissionsHandler: AllowPermissionsHandler,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
@ -492,6 +493,14 @@ internal class SwapInteractorImpl @Inject constructor(
},
ifRight = {
val timestamp = System.currentTimeMillis()
storeSwapTransaction(
currencyToSend = currencyToSend,
currencyToGet = currencyToGet,
amount = amount,
swapProvider = swapProvider,
swapDataModel = exchangeData.dataModel,
timestamp = timestamp,
)
TxState.TxSent(
fromAmount = amountFormatter.formatSwapAmountToUI(
amount,
@ -531,6 +540,28 @@ internal class SwapInteractorImpl @Inject constructor(
}
}
private suspend fun storeSwapTransaction(
currencyToSend: CryptoCurrencyStatus,
currencyToGet: CryptoCurrencyStatus,
amount: SwapAmount,
swapProvider: SwapProvider,
swapDataModel: SwapDataModel,
timestamp: Long,
) {
swapTransactionRepository.storeTransaction(
userWalletId = UserWalletId(userWalletManager.getWalletId()),
fromCryptoCurrencyId = currencyToSend.currency.id,
toCryptoCurrencyId = currencyToGet.currency.id,
transaction = SavedSwapTransactionModel(
txId = swapDataModel.transaction.txId,
provider = swapProvider,
timestamp = timestamp,
fromCryptoAmount = amount.value,
toCryptoAmount = swapDataModel.toTokenAmount.value,
),
)
}
@Deprecated("used in old swap mechanism")
override fun getTokenBalance(token: CryptoCurrencyStatus): SwapAmount {
return SwapAmount(token.value.amount ?: BigDecimal.ZERO, getTokenDecimals(token.currency))

View file

@ -34,9 +34,10 @@ class SwapDomainModule {
userWalletManager: UserWalletManager,
transactionManager: TransactionManager,
@SwapScope getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
@SwapScope getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
@SwapScope sendTransactionUseCase: SendTransactionUseCase,
quotesRepository: QuotesRepository,
swapTransactionRepository: SwapTransactionRepository,
walletManagersFacade: WalletManagersFacade,
coroutineDispatcherProvider: CoroutineDispatcherProvider,
): SwapInteractor {
@ -51,6 +52,7 @@ class SwapDomainModule {
quotesRepository = quotesRepository,
walletManagersFacade = walletManagersFacade,
dispatcher = coroutineDispatcherProvider,
swapTransactionRepository = swapTransactionRepository,
)
}
@ -69,7 +71,6 @@ class SwapDomainModule {
return GetSelectedWalletSyncUseCase(walletsStateHolder = walletsStateHolder)
}
@SwapScope
@Provides
@Singleton
fun providesGetCryptoCurrencyStatusUseCase(

View file

@ -1,5 +1,7 @@
package com.tangem.feature.swap.domain.models.domain
import java.math.BigDecimal
data class SavedSwapTransactionListModel(
val userWalletId: String,
val fromCryptoCurrencyId: String,
@ -10,10 +12,8 @@ data class SavedSwapTransactionListModel(
data class SavedSwapTransactionModel(
val txId: String,
val timestamp: Long,
val fromCryptoAmount: String,
val toCryptoAmount: String,
val fromCryptoAmount: BigDecimal,
val toCryptoAmount: BigDecimal,
val provider: SwapProvider,
val toFiatAmount: String? = null,
val fromFiatAmount: String? = null,
val status: ExchangeStatusModel? = null,
)

View file

@ -8,6 +8,7 @@ data class SwapSuccessStateHolder(
val txUrl: String,
val fee: TextReference,
val rate: TextReference,
val showStatusButton: Boolean,
val providerName: TextReference,
val providerType: TextReference,
val providerIcon: String,

View file

@ -14,6 +14,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.swap.converters.TokensDataConverter
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
import com.tangem.feature.swap.domain.models.domain.SwapProvider
import com.tangem.feature.swap.domain.models.formatToUIRepresentation
@ -592,6 +593,7 @@ internal class StateBuilder(
txUrl = txUrl,
providerName = TextReference.Str(providerState.name),
providerType = TextReference.Str(providerState.type),
showStatusButton = providerState.type == ExchangeProviderType.CEX.name,
providerIcon = providerState.iconUrl,
fee = TextReference.Str("${fee.amountCrypto} ${fee.symbolCrypto} (${fee.amountFiatFormatted})"),
rate = TextReference.Str(providerState.rate),

View file

@ -43,6 +43,7 @@ fun SwapSuccessScreen(state: SwapSuccessStateHolder, onBack: () -> Unit) {
SwapSuccessScreenButtons(
textRes = R.string.common_close,
txUrl = state.txUrl,
showStatusButton = state.showStatusButton,
onExploreClick = state.onSecondaryButtonClick,
onDoneClick = onBack,
)
@ -105,6 +106,7 @@ private fun SwapSuccessScreenContent(state: SwapSuccessStateHolder, padding: Pad
private fun SwapSuccessScreenButtons(
@StringRes textRes: Int,
txUrl: String,
showStatusButton: Boolean,
onExploreClick: () -> Unit,
onDoneClick: () -> Unit,
) {
@ -119,21 +121,23 @@ private fun SwapSuccessScreenButtons(
if (txUrl.isNotBlank()) {
Row {
SecondaryButtonIconStart(
text = stringResource(id = com.tangem.core.ui.R.string.common_explore),
iconResId = com.tangem.core.ui.R.drawable.ic_web_24,
text = stringResource(id = R.string.common_explore),
iconResId = R.drawable.ic_web_24,
onClick = onExploreClick,
modifier = Modifier.weight(1f),
)
SpacerW12()
SecondaryButtonIconStart(
text = stringResource(id = com.tangem.core.ui.R.string.common_share),
iconResId = com.tangem.core.ui.R.drawable.ic_share_24,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
context.shareText(txUrl)
},
modifier = Modifier.weight(1f),
)
if (showStatusButton) {
SpacerW12()
SecondaryButtonIconStart(
text = stringResource(id = R.string.express_cex_status_button_title),
iconResId = R.drawable.ic_arrow_top_right_24,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
context.shareText(txUrl)
},
modifier = Modifier.weight(1f),
)
}
}
SpacerH12()
}
@ -154,6 +158,7 @@ private val state = SwapSuccessStateHolder(
fee = TextReference.Str("1 000 DAI ~ 1 000 MATIC"),
providerName = TextReference.Str("1inch"),
providerType = TextReference.Str(ExchangeProviderType.DEX.name),
showStatusButton = false,
providerIcon = "",
fromTokenAmount = TextReference.Str("1 000 DAI"),
toTokenAmount = TextReference.Str("1 000 MATIC"),

View file

@ -37,6 +37,7 @@ dependencies {
implementation(deps.tangem.blockchain)
implementation(deps.tangem.card.core)
implementation(deps.timber)
implementation(deps.lifecycle.compose)
/** DI */
implementation(deps.hilt.android)
@ -66,6 +67,9 @@ dependencies {
implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models)
/** Temp dependency to swap domain */
implementation(projects.features.swap.domain)
/** Feature Apis */
implementation(projects.features.tokendetails.api)
implementation(projects.features.send.api)

View file

@ -1,24 +1,28 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state
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.collections.immutable.PersistentList
import kotlinx.coroutines.flow.MutableStateFlow
internal data class SwapTransactionsState(
val txId: String,
val provider: SwapProvider,
val txUrl: String? = null,
val timestamp: Long,
val statuses: PersistentList<ExchangeStatusState>,
val activeStatus: ExchangeStatus?,
val timestamp: TextReference,
val fiatSymbol: String,
val notification: ExchangeStatusNotifications? = null,
val activeStatus: MutableStateFlow<ExchangeStatus?>,
val statuses: MutableStateFlow<List<ExchangeStatusState>>,
val notification: MutableStateFlow<ExchangeStatusNotifications?> = MutableStateFlow(null),
val toCryptoCurrencyId: CryptoCurrency.ID,
val toCryptoAmount: String,
val toCryptoSymbol: String,
val toFiatAmount: String,
val toCurrencyIcon: TokenIconState,
val fromCryptoCurrencyId: CryptoCurrency.ID,
val fromCryptoAmount: String,
val fromCryptoSymbol: String,
val fromFiatAmount: String,
@ -29,6 +33,7 @@ internal data class SwapTransactionsState(
internal class ExchangeStatusState(
val status: ExchangeStatus,
val text: TextReference,
val isActive: Boolean,
val isDone: Boolean,
)

View file

@ -27,6 +27,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadedTxHistoryConverter
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadingTxHistoryConverter
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadingTxHistoryConverter.TokenDetailsLoadingTxHistoryModel
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
@ -242,6 +243,19 @@ internal class TokenDetailsStateFactory(
return state.copy(notifications = notificationConverter.removeRentInfo(state))
}
fun getStateWithExchangeStatusBottomSheet(txId: String): TokenDetailsState {
val state = currentStateProvider()
return state.copy(
bottomSheetConfig = TangemBottomSheetConfig(
isShow = true,
onDismissRequest = clickIntents::onDismissBottomSheet,
content = ExchangeStatusBottomSheetConfig(
value = state.swapTxs.first { it.txId == txId },
),
),
)
}
fun getStateAndTriggerEvent(
state: TokenDetailsState,
errorMessage: TextReference,

View file

@ -0,0 +1,227 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
import com.tangem.common.Provider
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.toDateFormat
import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel
import com.tangem.feature.tokendetails.presentation.tokendetails.state.ExchangeStatusState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTransactionsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications
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.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(
private val clickIntents: TokenDetailsClickIntents,
appCurrencyProvider: Provider<AppCurrency>,
) : Converter<Unit, PersistentList<SwapTransactionsState>> {
private val iconStateConverter = CryptoCurrencyToIconStateConverter()
private val appCurrency = appCurrencyProvider()
override fun convert(value: Unit): PersistentList<SwapTransactionsState> {
return persistentListOf()
}
fun convert(
savedTransactions: List<SavedSwapTransactionListModel>,
cryptoStatusList: List<CryptoCurrencyStatus>,
): PersistentList<SwapTransactionsState> {
val result = mutableListOf<SwapTransactionsState>()
savedTransactions.forEach { swapCurrency ->
val firstStatus = cryptoStatusList.first { it.currency.id.value == swapCurrency.fromCryptoCurrencyId }
val secondStatus = cryptoStatusList.first { it.currency.id.value == swapCurrency.toCryptoCurrencyId }
val (fromCurrency, toCurrency) = if (swapCurrency.fromCryptoCurrencyId == firstStatus.currency.id.value) {
firstStatus to secondStatus
} else {
secondStatus to firstStatus
}
swapCurrency.transactions.forEach { transaction ->
val toAmount = transaction.toCryptoAmount
val fromAmount = transaction.fromCryptoAmount
val toFiatAmount = toAmount.multiply(toCurrency.value.fiatRate)
val fromFiatAmount = fromAmount.multiply(fromCurrency.value.fiatRate)
val timestamp = transaction.timestamp
result.add(
SwapTransactionsState(
txId = transaction.txId,
provider = transaction.provider,
txUrl = transaction.status?.txUrl,
timestamp = TextReference.Str("${timestamp.toDateFormat()}, ${timestamp.toTimeFormat()}"),
fiatSymbol = appCurrency.symbol,
statuses = MutableStateFlow(getStatuses(transaction.status?.status)),
activeStatus = MutableStateFlow(transaction.status?.status),
notification = MutableStateFlow(
getNotification(
transaction.status?.status,
transaction.status?.txUrl,
),
),
toCryptoCurrencyId = toCurrency.currency.id,
toCryptoAmount = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = toAmount,
cryptoCurrency = toCurrency.currency,
),
toCryptoSymbol = toCurrency.currency.symbol,
toFiatAmount = getFiatAmount(toFiatAmount),
toCurrencyIcon = iconStateConverter.convert(toCurrency),
fromCryptoCurrencyId = fromCurrency.currency.id,
fromCryptoAmount = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = fromAmount,
cryptoCurrency = fromCurrency.currency,
),
fromCryptoSymbol = fromCurrency.currency.symbol,
fromFiatAmount = getFiatAmount(fromFiatAmount),
fromCurrencyIcon = iconStateConverter.convert(fromCurrency),
onClick = { clickIntents.onSwapTransactionClick(transaction.txId) },
onGoToProviderClick = { clickIntents.onGoToProviderClick(transaction.status?.txUrl.orEmpty()) },
),
)
}
}
return result.toPersistentList()
}
fun updateTxStatus(tx: SwapTransactionsState, status: ExchangeStatus?) {
if (tx.activeStatus.value == status) return
tx.activeStatus.update { status }
tx.notification.update { getNotification(status, tx.txUrl) }
tx.statuses.update { getStatuses(status) }
}
private fun getFiatAmount(toFiatAmount: BigDecimal): String {
return BigDecimalFormatter.formatFiatAmount(
fiatAmount = toFiatAmount,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
}
private fun getNotification(status: ExchangeStatus?, txUrl: String?): ExchangeStatusNotifications? {
if (txUrl == null) return null
return when (status) {
ExchangeStatus.Failed -> ExchangeStatusNotifications.Failed {
clickIntents.onGoToProviderClick(txUrl)
}
ExchangeStatus.Verifying -> ExchangeStatusNotifications.NeedVerification {
clickIntents.onGoToProviderClick(txUrl)
}
else -> null
}
}
private fun getStatuses(status: ExchangeStatus?): List<ExchangeStatusState> {
val isWaiting = status == ExchangeStatus.New || status == ExchangeStatus.Waiting
val isConfirming = status == ExchangeStatus.Confirming
val isVerifying = status == ExchangeStatus.Verifying
val isExchanging = status == ExchangeStatus.Exchanging
val isFailed = status == ExchangeStatus.Failed
val isSending = status == ExchangeStatus.Sending
val isWaitingDone = !isWaiting
val isConfirmingDone = !isConfirming && isWaitingDone
val isExchangingDone = !isExchanging && isConfirmingDone
val isSendingDone = !isSending && !isVerifying && !isFailed && isExchangingDone
return listOf(
waitStep(isWaiting, isWaitingDone),
confirmStep(isConfirming, isConfirmingDone),
exchangeStep(isExchanging, isExchangingDone, isVerifying, isFailed),
sendStep(isSending, isSendingDone),
)
}
private fun waitStep(isNew: Boolean, isNewDone: Boolean) = ExchangeStatusState(
status = ExchangeStatus.New,
text = when {
isNew -> combinedReference(
TextReference.Res(R.string.express_exchange_status_receiving),
TextReference.Str(STATUS_ACTIVE_DOTS),
)
else -> TextReference.Res(R.string.express_exchange_status_receiving)
},
isActive = isNew,
isDone = isNewDone,
)
private fun confirmStep(isConfirming: Boolean, isConfirmingDone: Boolean) = ExchangeStatusState(
status = ExchangeStatus.Confirming,
text = when {
isConfirming -> combinedReference(
TextReference.Res(R.string.express_exchange_status_confirming),
TextReference.Str(STATUS_ACTIVE_DOTS),
)
isConfirmingDone -> TextReference.Res(R.string.express_exchange_status_confirmed)
else -> TextReference.Res(R.string.express_exchange_status_confirming)
},
isActive = isConfirming,
isDone = isConfirmingDone,
)
private fun exchangeStep(
isExchanging: Boolean,
isExchangingDone: Boolean,
isVerifying: Boolean = false,
isFailed: Boolean = false,
) = when {
isVerifying -> ExchangeStatusState(
status = ExchangeStatus.Verifying,
text = TextReference.Res(R.string.express_exchange_status_verifying),
isActive = true,
isDone = false,
)
isFailed -> ExchangeStatusState(
status = ExchangeStatus.Failed,
text = TextReference.Res(R.string.express_exchange_status_failed),
isActive = true,
isDone = false,
)
else -> ExchangeStatusState(
status = ExchangeStatus.Exchanging,
text = when {
isExchanging -> combinedReference(
TextReference.Res(R.string.express_exchange_status_exchanging),
TextReference.Str(STATUS_ACTIVE_DOTS),
)
isExchangingDone -> TextReference.Res(R.string.express_exchange_status_exchanged)
else -> TextReference.Res(R.string.express_exchange_status_exchanging)
},
isActive = isExchanging,
isDone = isExchangingDone,
)
}
private fun sendStep(isSending: Boolean, isSendingDone: Boolean) = ExchangeStatusState(
status = ExchangeStatus.Sending,
text = when {
isSending -> combinedReference(
TextReference.Res(R.string.express_exchange_status_sending),
TextReference.Str(STATUS_ACTIVE_DOTS),
)
isSendingDone -> TextReference.Res(R.string.express_exchange_status_sent)
else -> TextReference.Res(R.string.express_exchange_status_sending)
},
isActive = isSending,
isDone = isSendingDone,
)
private companion object {
private const val STATUS_ACTIVE_DOTS = ""
}
}

View file

@ -22,7 +22,9 @@ internal fun ExchangeEstimate(
fromTokenIconState: TokenIconState,
toTokenIconState: TokenIconState,
fromCryptoAmount: TextReference,
fromCryptoSymbol: String,
toCryptoAmount: TextReference,
toCryptoSymbol: String,
fromFiatAmount: TextReference,
toFiatAmount: TextReference,
modifier: Modifier = Modifier,
@ -59,9 +61,11 @@ internal fun ExchangeEstimate(
leftIcon = fromTokenIconState,
leftTitle = fromCryptoAmount,
leftSubtitle = fromFiatAmount,
leftTitleEllipsisOffset = fromCryptoSymbol.length,
rightIcon = toTokenIconState,
rightTitle = toCryptoAmount,
rightSubtitle = toFiatAmount,
rightTitleEllipsisOffset = toCryptoSymbol.length,
)
}
}

View file

@ -17,19 +17,22 @@ 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.collections.immutable.PersistentList
import kotlinx.coroutines.flow.MutableStateFlow
@Composable
internal fun ExchangeStatusBlock(
statuses: PersistentList<ExchangeStatusState>,
statuses: MutableStateFlow<List<ExchangeStatusState>>,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val statusValues = statuses.collectAsStateWithLifecycle()
Column(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
@ -70,10 +73,10 @@ internal fun ExchangeStatusBlock(
}
}
statuses.forEachIndexed { index, item ->
statusValues.value.forEachIndexed { index, item ->
ExchangeStatusStep(
stepStatus = item,
isLast = index == statuses.lastIndex,
isLast = index == statusValues.value.lastIndex,
)
}
}
@ -128,7 +131,7 @@ private fun ExchangeStatusStepText(stepStatus: ExchangeStatusState) {
}
Text(
text = getStatusText(stepStatus)?.let { stringResource(it) }.orEmpty(),
text = stepStatus.text.resolveReference(),
style = TangemTheme.typography.body2,
color = textColor,
modifier = Modifier
@ -206,34 +209,4 @@ private fun ExchangeStepSeparator() {
shape = CircleShape,
),
)
}
private fun getStatusText(stepStatus: ExchangeStatusState) = when (stepStatus.status) {
ExchangeStatus.Failed -> R.string.express_exchange_status_failed
ExchangeStatus.Verifying -> if (stepStatus.isDone) {
R.string.express_exchange_status_verified
} else {
R.string.express_exchange_status_verifying
}
ExchangeStatus.New, ExchangeStatus.Waiting -> if (stepStatus.isDone) {
R.string.express_exchange_status_received
} else {
R.string.express_exchange_status_receiving
}
ExchangeStatus.Confirming -> if (stepStatus.isDone) {
R.string.express_exchange_status_confirmed
} else {
R.string.express_exchange_status_confirming
}
ExchangeStatus.Exchanging -> if (stepStatus.isDone) {
R.string.express_exchange_status_exchanged
} else {
R.string.express_exchange_status_exchanging
}
ExchangeStatus.Sending -> if (stepStatus.isDone) {
R.string.express_exchange_status_sent
} else {
R.string.express_exchange_status_sending
}
else -> null
}

View file

@ -8,6 +8,7 @@ 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
@ -19,8 +20,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.toDateFormat
import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTransactionsState
@Composable
@ -36,6 +36,8 @@ 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),
) {
@ -55,13 +57,14 @@ private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetC
.align(CenterHorizontally),
)
SpacerH16()
val timestamp = config.timestamp
ExchangeEstimate(
timestamp = TextReference.Str("${timestamp.toDateFormat()}, ${timestamp.toTimeFormat()}"),
timestamp = config.timestamp,
fromTokenIconState = config.fromCurrencyIcon,
toTokenIconState = config.toCurrencyIcon,
fromCryptoAmount = TextReference.Str(config.fromCryptoAmount),
fromCryptoSymbol = config.fromCryptoSymbol,
toCryptoAmount = TextReference.Str(config.toCryptoAmount),
toCryptoSymbol = config.toCryptoSymbol,
fromFiatAmount = TextReference.Str(config.fromFiatAmount),
toFiatAmount = TextReference.Str(config.toFiatAmount),
)
@ -77,12 +80,20 @@ private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetC
onClick = config.onGoToProviderClick,
)
AnimatedContent(
targetState = config.notification,
targetState = notification.value,
label = "Exchange Status Notification Change",
) {
it?.let {
SpacerH12()
Notification(config = it.config)
val tint = when (status.value) {
ExchangeStatus.Verifying -> TangemTheme.colors.icon.attention
ExchangeStatus.Failed -> TangemTheme.colors.icon.warning
else -> null
}
Notification(
config = it.config,
iconTint = tint,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing12),
)
}
}
SpacerH24()

View file

@ -4,9 +4,7 @@ import androidx.annotation.DrawableRes
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@ -16,9 +14,11 @@ 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.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import androidx.constraintlayout.compose.Visibility
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 +41,8 @@ internal fun LazyListScope.swapTransactionsItems(
contentType = { swapTxs[it]::class.java },
) {
val item = swapTxs[it]
val (iconRes, tint) = when (item.activeStatus) {
val status = item.activeStatus.collectAsStateWithLifecycle()
val (iconRes, tint) = when (status.value) {
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
@ -54,7 +54,6 @@ internal fun LazyListScope.swapTransactionsItems(
toTokenIconState = item.toCurrencyIcon,
fromAmount = item.fromCryptoAmount,
fromSymbol = item.fromCryptoSymbol,
toAmount = item.toCryptoAmount,
toSymbol = item.toCryptoSymbol,
onClick = item.onClick,
infoIconRes = iconRes,
@ -73,7 +72,6 @@ private fun ExchangeStatusItem(
toTokenIconState: TokenIconState,
fromAmount: String,
fromSymbol: String,
toAmount: String,
toSymbol: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
@ -121,7 +119,7 @@ private fun ExchangeStatusItem(
top.linkTo(titleRef.bottom, padding6)
end.linkTo(swapIconRef.start)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
width = Dimension.fillToConstraints.atMostWrapContent
},
)
Icon(
@ -149,17 +147,16 @@ private fun ExchangeStatusItem(
bottom.linkTo(parent.bottom)
},
)
EllipsisText(
text = toAmount,
Text(
text = toSymbol,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
ellipsis = TextEllipsis.OffsetEnd(toSymbol.length),
modifier = Modifier.constrainAs(toRef) {
start.linkTo(toIconRef.end, padding6)
top.linkTo(titleRef.bottom, padding6)
end.linkTo(infoIconRef.start, padding6)
end.linkTo(infoIconRef.start, padding6, padding6)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
width = Dimension.fillToConstraints.atLeastWrapContent
},
)
Icon(
@ -192,4 +189,54 @@ private fun ExchangeStatusItem(
},
)
}
}
}
//region Preview
@Preview
@Composable
private fun ExchangeStatusItemPreview_Light(
@PreviewParameter(ExchangeStatusItemsPreviewParameterProvider::class) amount: String,
) {
TangemTheme {
ExchangeStatusItem(
providerName = "ChangeNow",
fromTokenIconState = TokenIconState.Loading,
toTokenIconState = TokenIconState.Loading,
fromAmount = amount,
fromSymbol = "USDT",
toSymbol = "USDT",
onClick = {},
infoIconRes = null,
infoIconTint = null,
)
}
}
@Preview
@Composable
private fun ExchangeStatusItemPreview_Dark(
@PreviewParameter(ExchangeStatusItemsPreviewParameterProvider::class) amount: String,
) {
TangemTheme(isDark = true) {
ExchangeStatusItem(
providerName = "ChangeNow",
fromTokenIconState = TokenIconState.Loading,
toTokenIconState = TokenIconState.Loading,
fromAmount = amount,
fromSymbol = "USDT",
toSymbol = "USDT",
onClick = {},
infoIconRes = null,
infoIconTint = null,
)
}
}
private class ExchangeStatusItemsPreviewParameterProvider : PreviewParameterProvider<String> {
override val values: Sequence<String>
get() = sequenceOf(
"1111111111111111111111111111 USDT",
"11111 USDT",
)
}
//endregion

View file

@ -0,0 +1,141 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels
import arrow.core.getOrElse
import com.tangem.common.Provider
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.feature.swap.domain.SwapRepository
import com.tangem.feature.swap.domain.SwapTransactionRepository
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
import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTransactionsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsSwapTransactionsStateConverter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
@Suppress("LongParameterList")
internal class ExchangeStatusFactory(
private val swapTransactionRepository: SwapTransactionRepository,
private val swapRepository: SwapRepository,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
private val dispatchers: CoroutineDispatcherProvider,
private val clickIntents: TokenDetailsClickIntents,
private val appCurrencyProvider: Provider<AppCurrency>,
private val userWalletId: UserWalletId,
private val cryptoCurrencyId: CryptoCurrency.ID,
) {
private val swapTransactionsStateConverter by lazy {
TokenDetailsSwapTransactionsStateConverter(
clickIntents = clickIntents,
appCurrencyProvider = appCurrencyProvider,
)
}
operator fun invoke() = combine(
flow = swapTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId),
flow2 = getWalletCryptoCurrencies().conflate(),
) { savedTransactions, cryptoCurrenciesStatusList ->
innerLoadSwapState(
savedTransactions,
cryptoCurrenciesStatusList,
)
}
private fun getWalletCryptoCurrencies() = flow {
val selectedWallet = getSelectedWalletSyncUseCase().fold(
ifLeft = { null },
ifRight = { it },
)
requireNotNull(selectedWallet) { "No selected wallet" }
val cryptoCurrenciesList = getMultiCryptoCurrencyStatusUseCase(selectedWallet.walletId)
.getOrElse { emptyList() }
emit(cryptoCurrenciesList)
}
suspend fun updateSwapTxStatuses(swapTxList: PersistentList<SwapTransactionsState>) = withContext(dispatchers.io) {
swapTxList.map { tx ->
async {
val status = getExchangeStatus(tx.txId)?.status
swapTransactionsStateConverter.updateTxStatus(tx, status)
tx.removeIfFinished(status)
}
}
.awaitAll()
.filterNotNull()
.toPersistentList()
}
private suspend fun innerLoadSwapState(
savedTransactions: List<SavedSwapTransactionListModel>?,
cryptoCurrenciesStatusList: List<CryptoCurrencyStatus>,
): PersistentList<SwapTransactionsState> {
val txWithStatuses = savedTransactions?.map { currencySwaps ->
currencySwaps.copy(
transactions = currencySwaps.transactions.map { tx ->
val status = getExchangeStatus(tx.txId)
tx.copy(
status = status,
)
},
)
}
return getExchangeStatusState(
savedTransactions = txWithStatuses,
cryptoCurrencyStatusList = cryptoCurrenciesStatusList,
)
}
private suspend fun getExchangeStatus(txId: String): ExchangeStatusModel? {
return swapRepository.getExchangeStatus(txId)
.fold(
ifLeft = { null },
ifRight = { it },
)
}
private fun getExchangeStatusState(
savedTransactions: List<SavedSwapTransactionListModel>?,
cryptoCurrencyStatusList: List<CryptoCurrencyStatus>,
): PersistentList<SwapTransactionsState> {
if (savedTransactions == null || cryptoCurrencyStatusList.isEmpty()) {
return persistentListOf()
}
return swapTransactionsStateConverter.convert(
savedTransactions = savedTransactions,
cryptoStatusList = cryptoCurrencyStatusList,
)
}
private suspend fun SwapTransactionsState.removeIfFinished(status: ExchangeStatus?): SwapTransactionsState? {
return if (status == ExchangeStatus.Refunded || status == ExchangeStatus.Finished) {
swapTransactionRepository.removeTransaction(
userWalletId = userWalletId,
fromCryptoCurrencyId = fromCryptoCurrencyId,
toCryptoCurrencyId = toCryptoCurrencyId,
txId = txId,
)
null
} else {
this
}
}
}

View file

@ -38,4 +38,8 @@ interface TokenDetailsClickIntents {
fun onDismissBottomSheet()
fun onCloseRentInfoNotification()
fun onSwapTransactionClick(txId: String)
fun onGoToProviderClick(url: String)
}

View file

@ -31,16 +31,19 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.swap.domain.SwapRepository
import com.tangem.feature.swap.domain.SwapTransactionRepository
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
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.features.tokendetails.impl.R
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import com.tangem.utils.coroutines.*
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.PersistentList
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.*
@ -50,7 +53,7 @@ import java.math.BigDecimal
import javax.inject.Inject
import kotlin.properties.Delegates
@Suppress("LongParameterList", "LargeClass")
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions")
@HiltViewModel
internal class TokenDetailsViewModel @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider,
@ -67,6 +70,10 @@ internal class TokenDetailsViewModel @Inject constructor(
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getCurrencyWarningsUseCase: GetCurrencyWarningsUseCase,
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
private val swapRepository: SwapRepository,
private val swapTransactionRepository: SwapTransactionRepository,
private val walletManagersFacade: WalletManagersFacade,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val reduxStateHolder: ReduxStateHolder,
@ -86,8 +93,11 @@ internal class TokenDetailsViewModel @Inject constructor(
private val marketPriceJobHolder = JobHolder()
private val refreshStateJobHolder = JobHolder()
private val warningsJobHolder = JobHolder()
private val swapTxJobHolder = JobHolder()
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
private var swapTxStatusTaskScheduler = SingleTaskScheduler<PersistentList<SwapTransactionsState>>()
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
private val stateFactory = TokenDetailsStateFactory(
@ -98,6 +108,20 @@ internal class TokenDetailsViewModel @Inject constructor(
decimals = cryptoCurrency.decimals,
)
private val exchangeStatusFactory by lazy {
ExchangeStatusFactory(
swapTransactionRepository = swapTransactionRepository,
swapRepository = swapRepository,
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
getMultiCryptoCurrencyStatusUseCase = getMultiCryptoCurrencyStatusUseCase,
dispatchers = dispatchers,
clickIntents = this,
appCurrencyProvider = Provider { selectedAppCurrencyFlow.value },
userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrency.id,
)
}
var uiState: TokenDetailsState by mutableStateOf(stateFactory.getInitialState(cryptoCurrency))
private set
@ -109,8 +133,14 @@ internal class TokenDetailsViewModel @Inject constructor(
handleBalanceHiding(owner)
}
override fun onCleared() {
swapTxStatusTaskScheduler.cancelTask()
super.onCleared()
}
private fun updateContent() {
subscribeOnCurrencyStatusUpdates()
subscribeOnExchangeTransactionsUpdates()
updateTxHistory(refresh = false, showItemsLoading = true)
}
@ -177,6 +207,33 @@ internal class TokenDetailsViewModel @Inject constructor(
}
}
private fun subscribeOnExchangeTransactionsUpdates() {
viewModelScope.launch(dispatchers.io) {
swapTxStatusTaskScheduler.cancelTask()
exchangeStatusFactory.invoke()
.onEach { swapTxs ->
swapTxStatusTaskScheduler.scheduleTask(
viewModelScope,
PeriodicTask(
delay = EXCHANGE_STATUS_UPDATE_DELAY,
task = {
runCatching(dispatchers.io) {
exchangeStatusFactory.updateSwapTxStatuses(swapTxs)
}
},
onSuccess = { updatedTxs ->
uiState = uiState.copy(swapTxs = updatedTxs)
},
onError = {},
),
)
}
.flowOn(dispatchers.io)
.launchIn(viewModelScope)
.saveIn(swapTxJobHolder)
}
}
/**
* @param refresh - invalidate cache and get data from remote
* @param showItemsLoading - show loading items placeholder.
@ -475,35 +532,16 @@ internal class TokenDetailsViewModel @Inject constructor(
override fun onCloseRentInfoNotification() {
uiState = stateFactory.getStateWithRemovedRentNotification()
}
}
//
// val text = when (stepStatus.status) {
// ExchangeStatus.Failed -> stringResource(id = R.string.express_exchange_status_failed)
// ExchangeStatus.Verifying -> if (stepStatus.isDone) {
// stringResource(id = R.string.express_exchange_status_verified)
// } else {
// stringResource(id = R.string.express_exchange_status_verifying)
// }
// ExchangeStatus.New, ExchangeStatus.Waiting -> if (stepStatus.isDone) {
// stringResource(id = R.string.express_exchange_status_received)
// } else {
// stringResource(id = R.string.express_exchange_status_receiving)
// }
// ExchangeStatus.Confirming -> if (stepStatus.isDone) {
// stringResource(id = R.string.express_exchange_status_confirmed)
// } else {
// stringResource(id = R.string.express_exchange_status_confirming)
// }
// ExchangeStatus.Exchanging -> if (stepStatus.isDone) {
// stringResource(id = R.string.express_exchange_status_exchanged)
// } else {
// stringResource(id = R.string.express_exchange_status_exchanging)
// }
// ExchangeStatus.Sending -> if (stepStatus.isDone) {
// stringResource(id = R.string.express_exchange_status_sent)
// } else {
// stringResource(id = R.string.express_exchange_status_sending)
// }
// else -> ""
// }
override fun onSwapTransactionClick(txId: String) {
uiState = stateFactory.getStateWithExchangeStatusBottomSheet(txId)
}
override fun onGoToProviderClick(url: String) {
router.openUrl(url)
}
private companion object {
const val EXCHANGE_STATUS_UPDATE_DELAY = 10_000L
}
}