Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-01 17:24:50 +03:00
parent 199c2795ad
commit d22cc0fe8b
9 changed files with 124 additions and 42 deletions

View file

@ -198,9 +198,9 @@
<string name="express_choose_providers_subtitle">Провайдеры проводят транзакции, обеспечивая плавный и эффективный обмен токенами</string>
<string name="express_choose_providers_title">Выберите провайдера</string>
<string name="express_exchange_notification_failed_text">Чтобы узнать причину, посетите сайт провайдера</string>
<string name="express_exchange_notification_failed_title">Операция не выполнена провайдером</string>
<string name="express_exchange_notification_failed_title">Чтобы вернуть ваши деньги, посетите сайт провайдера</string>
<string name="express_exchange_notification_verification_text">Посетите сайт провайдера для проверки</string>
<string name="express_exchange_notification_verification_title">Провайдер: требуется верификация</string>
<string name="express_exchange_notification_verification_title">Провайдер запрашивает прохождение верификации</string>
<string name="express_exchange_token_list_subtitle">Список токенов в вашем кошельке</string>
<string name="express_fetch_best_rates">Получение наилучших курсов...</string>
<string name="express_provider">Провайдер</string>

View file

@ -198,18 +198,22 @@
<string name="express_choose_providers_title">Choose provider</string>
<string name="express_estimated_amount">Estimated amount</string>
<string name="express_exchange_by">Exchange by %s</string>
<string name="express_exchange_notification_failed_text">Visit providers website to see why</string>
<string name="express_exchange_notification_failed_text">Visit providers website to refund your money</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">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_confirming_active">Confirming…</string>
<string name="express_exchange_status_exchanged">Exchanged</string>
<string name="express_exchange_status_exchanging">Exchanging</string>
<string name="express_exchange_status_exchanging_active">Exchanging…</string>
<string name="express_exchange_status_failed">Failed</string>
<string name="express_exchange_status_received">Deposit received</string>
<string name="express_exchange_status_receiving">Awaiting deposit</string>
<string name="express_exchange_status_receiving_active">Awaiting deposit…</string>
<string name="express_exchange_status_sending">Sending to you</string>
<string name="express_exchange_status_sending_active">Sending to you…</string>
<string name="express_exchange_status_sent">Sent</string>
<string name="express_exchange_status_subtitle">Provider-sourced data. Estimated amount subject to change.</string>
<string name="express_exchange_status_title">Exchange status</string>

View file

@ -0,0 +1,44 @@
package com.tangem.domain.tokens.models.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
class TokenExchangeAnalyticsEvent(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("Token", event, params, null) {
class CexTx(token: String) : TokenScreenAnalyticsEvent(
event = "Notice - ChangeNow Swap",
params = mapOf("Token" to token),
)
class CexTxOpened(token: String, status: String) : TokenScreenAnalyticsEvent(
event = "ChangeNow Swap Opened",
params = mapOf("Token" to token, "Status" to status),
)
class Verification(token: String) : TokenScreenAnalyticsEvent(
event = "Notice - KYC required",
params = mapOf("Token" to token),
)
class Fail(token: String) : TokenScreenAnalyticsEvent(
event = "Notice - Operation Fail",
params = mapOf("Token" to token),
)
class GoToProviderStatus(token: String) : TokenScreenAnalyticsEvent(
event = "Button - Go To Provider",
params = mapOf("Token" to token, "Place" to "Status"),
)
class GoToProviderKYC(token: String) : TokenScreenAnalyticsEvent(
event = "Button - Go To Provider",
params = mapOf("Token" to token, "Place" to "KYC"),
)
class GoToProviderFail(token: String) : TokenScreenAnalyticsEvent(
event = "Button - Go To Provider",
params = mapOf("Token" to token, "Place" to "Fail"),
)
}

View file

@ -97,11 +97,11 @@ class DefaultSwapTransactionRepository(
)
}
?.transactions
?.dropWhile { it.txId == txId }
?.filterNot { it.txId == txId }
val editedList =
if (tokenTransactions.isNullOrEmpty()) {
savedList?.dropWhile {
savedList?.filterNot {
it.checkId(
checkUserWalletId = userWalletId,
fromCurrencyId = fromCryptoCurrencyId,

View file

@ -22,6 +22,7 @@ import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryListError
import com.tangem.domain.txhistory.models.TxHistoryStateError
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.components.TokenDetailsDialogConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadedTxHistoryConverter
@ -243,14 +244,13 @@ internal class TokenDetailsStateFactory(
return state.copy(notifications = notificationConverter.removeRentInfo(state))
}
fun getStateWithExchangeStatusBottomSheet(txId: String): TokenDetailsState {
val state = currentStateProvider()
return state.copy(
fun getStateWithExchangeStatusBottomSheet(swapTxState: SwapTransactionsState): TokenDetailsState {
return currentStateProvider().copy(
bottomSheetConfig = TangemBottomSheetConfig(
isShow = true,
onDismissRequest = clickIntents::onDismissBottomSheet,
content = ExchangeStatusBottomSheetConfig(
value = state.swapTxs.first { it.txId == txId },
value = swapTxState,
),
),
)

View file

@ -1,14 +1,16 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
import com.tangem.common.Provider
import com.tangem.core.analytics.api.AnalyticsEventHandler
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.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.models.analytics.TokenExchangeAnalyticsEvent
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
@ -26,6 +28,8 @@ import java.math.BigDecimal
internal class TokenDetailsSwapTransactionsStateConverter(
private val clickIntents: TokenDetailsClickIntents,
private val cryptoCurrency: CryptoCurrency,
private val analyticsEventsHandlerProvider: Provider<AnalyticsEventHandler>,
appCurrencyProvider: Provider<AppCurrency>,
) : Converter<Unit, PersistentList<SwapTransactionsState>> {
@ -89,8 +93,13 @@ internal class TokenDetailsSwapTransactionsStateConverter(
fromCryptoSymbol = fromCurrency.currency.symbol,
fromFiatAmount = getFiatAmount(fromFiatAmount),
fromCurrencyIcon = iconStateConverter.convert(fromCurrency),
onClick = { clickIntents.onSwapTransactionClick(transaction.txId) },
onGoToProviderClick = { clickIntents.onGoToProviderClick(transaction.status?.txUrl.orEmpty()) },
onClick = { clickIntents.onSwapTransactionClick(transaction.txId, transaction.status?.status) },
onGoToProviderClick = {
analyticsEventsHandlerProvider().send(
TokenExchangeAnalyticsEvent.GoToProviderStatus(cryptoCurrency.symbol),
)
clickIntents.onGoToProviderClick(url = transaction.status?.txUrl.orEmpty())
},
),
)
}
@ -116,11 +125,27 @@ internal class TokenDetailsSwapTransactionsStateConverter(
private fun getNotification(status: ExchangeStatus?, txUrl: String?): ExchangeStatusNotifications? {
if (txUrl == null) return null
return when (status) {
ExchangeStatus.Failed -> ExchangeStatusNotifications.Failed {
clickIntents.onGoToProviderClick(txUrl)
ExchangeStatus.Failed -> {
analyticsEventsHandlerProvider().send(
TokenExchangeAnalyticsEvent.Fail(cryptoCurrency.symbol),
)
ExchangeStatusNotifications.Failed {
analyticsEventsHandlerProvider().send(
TokenExchangeAnalyticsEvent.GoToProviderFail(cryptoCurrency.symbol),
)
clickIntents.onGoToProviderClick(txUrl)
}
}
ExchangeStatus.Verifying -> ExchangeStatusNotifications.NeedVerification {
clickIntents.onGoToProviderClick(txUrl)
ExchangeStatus.Verifying -> {
analyticsEventsHandlerProvider().send(
TokenExchangeAnalyticsEvent.Verification(cryptoCurrency.symbol),
)
ExchangeStatusNotifications.NeedVerification {
analyticsEventsHandlerProvider().send(
TokenExchangeAnalyticsEvent.GoToProviderKYC(cryptoCurrency.symbol),
)
clickIntents.onGoToProviderClick(txUrl)
}
}
else -> null
}
@ -150,10 +175,7 @@ internal class TokenDetailsSwapTransactionsStateConverter(
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),
)
isNew -> TextReference.Res(R.string.express_exchange_status_receiving_active)
else -> TextReference.Res(R.string.express_exchange_status_receiving)
},
isActive = isNew,
@ -163,10 +185,7 @@ internal class TokenDetailsSwapTransactionsStateConverter(
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),
)
isConfirming -> TextReference.Res(R.string.express_exchange_status_confirming_active)
isConfirmingDone -> TextReference.Res(R.string.express_exchange_status_confirmed)
else -> TextReference.Res(R.string.express_exchange_status_confirming)
},
@ -195,10 +214,7 @@ internal class TokenDetailsSwapTransactionsStateConverter(
else -> ExchangeStatusState(
status = ExchangeStatus.Exchanging,
text = when {
isExchanging -> combinedReference(
TextReference.Res(R.string.express_exchange_status_exchanging),
TextReference.Str(STATUS_ACTIVE_DOTS),
)
isExchanging -> TextReference.Res(R.string.express_exchange_status_exchanging_active)
isExchangingDone -> TextReference.Res(R.string.express_exchange_status_exchanged)
else -> TextReference.Res(R.string.express_exchange_status_exchanging)
},
@ -210,18 +226,11 @@ internal class TokenDetailsSwapTransactionsStateConverter(
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),
)
isSending -> TextReference.Res(R.string.express_exchange_status_sending_active)
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

@ -2,6 +2,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels
import arrow.core.getOrElse
import com.tangem.common.Provider
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
@ -35,19 +36,22 @@ internal class ExchangeStatusFactory(
private val dispatchers: CoroutineDispatcherProvider,
private val clickIntents: TokenDetailsClickIntents,
private val appCurrencyProvider: Provider<AppCurrency>,
private val analyticsEventsHandlerProvider: Provider<AnalyticsEventHandler>,
private val userWalletId: UserWalletId,
private val cryptoCurrencyId: CryptoCurrency.ID,
private val cryptoCurrency: CryptoCurrency,
) {
private val swapTransactionsStateConverter by lazy {
TokenDetailsSwapTransactionsStateConverter(
clickIntents = clickIntents,
cryptoCurrency = cryptoCurrency,
appCurrencyProvider = appCurrencyProvider,
analyticsEventsHandlerProvider = analyticsEventsHandlerProvider,
)
}
operator fun invoke() = combine(
flow = swapTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId),
flow = swapTransactionRepository.getTransactions(userWalletId, cryptoCurrency.id),
flow2 = getWalletCryptoCurrencies().conflate(),
) { savedTransactions, cryptoCurrenciesStatusList ->
innerLoadSwapState(

View file

@ -2,6 +2,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels
import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
interface TokenDetailsClickIntents {
@ -39,7 +40,7 @@ interface TokenDetailsClickIntents {
fun onCloseRentInfoNotification()
fun onSwapTransactionClick(txId: String)
fun onSwapTransactionClick(txId: String, status: ExchangeStatus?)
fun onGoToProviderClick(url: String)
}

View file

@ -23,6 +23,7 @@ import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.models.analytics.TokenExchangeAnalyticsEvent
import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent
import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
@ -35,9 +36,11 @@ 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.swap.domain.models.domain.ExchangeStatus
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.components.ExchangeStatusNotifications
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory
import com.tangem.features.tokendetails.impl.R
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
@ -117,8 +120,9 @@ internal class TokenDetailsViewModel @Inject constructor(
dispatchers = dispatchers,
clickIntents = this,
appCurrencyProvider = Provider { selectedAppCurrencyFlow.value },
analyticsEventsHandlerProvider = Provider { analyticsEventsHandler },
userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrency.id,
cryptoCurrency = cryptoCurrency,
)
}
@ -212,6 +216,9 @@ internal class TokenDetailsViewModel @Inject constructor(
swapTxStatusTaskScheduler.cancelTask()
exchangeStatusFactory.invoke()
.onEach { swapTxs ->
if (swapTxs.isNotEmpty()) {
analyticsEventsHandler.send(TokenExchangeAnalyticsEvent.CexTx(cryptoCurrency.symbol))
}
swapTxStatusTaskScheduler.scheduleTask(
viewModelScope,
PeriodicTask(
@ -533,8 +540,21 @@ internal class TokenDetailsViewModel @Inject constructor(
uiState = stateFactory.getStateWithRemovedRentNotification()
}
override fun onSwapTransactionClick(txId: String) {
uiState = stateFactory.getStateWithExchangeStatusBottomSheet(txId)
override fun onSwapTransactionClick(txId: String, status: ExchangeStatus?) {
val swapTxState = uiState.swapTxs.first { it.txId == txId }
analyticsEventsHandler.send(
TokenExchangeAnalyticsEvent.CexTxOpened(cryptoCurrency.symbol, status?.name.orEmpty()),
)
when (swapTxState.notification.value) {
is ExchangeStatusNotifications.NeedVerification -> {
analyticsEventsHandler.send(TokenExchangeAnalyticsEvent.Verification(cryptoCurrency.symbol))
}
is ExchangeStatusNotifications.Failed -> {
analyticsEventsHandler.send(TokenExchangeAnalyticsEvent.Fail(cryptoCurrency.symbol))
}
else -> Unit
}
uiState = stateFactory.getStateWithExchangeStatusBottomSheet(swapTxState)
}
override fun onGoToProviderClick(url: String) {