Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-07 12:22:47 +03:00
commit dcdfac7ec8
9 changed files with 102 additions and 42 deletions

View file

@ -0,0 +1,23 @@
package com.tangem.datasource.di
import com.tangem.datasource.local.datastore.RuntimeDataStore
import com.tangem.datasource.local.swaptx.DefaultSwapTransactionStatusStore
import com.tangem.datasource.local.swaptx.SwapTransactionStatusStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object SwapTransactionStatusStoreModule {
@Provides
@Singleton
fun provideSwapTransactionStatusStore(): SwapTransactionStatusStore {
return DefaultSwapTransactionStatusStore(
dataStore = RuntimeDataStore(),
)
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.datasource.local.swaptx
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
internal class DefaultSwapTransactionStatusStore(
private val dataStore: StringKeyDataStore<ExchangeAnalyticsStatus>,
) : SwapTransactionStatusStore, StringKeyDataStore<ExchangeAnalyticsStatus> by dataStore {
override suspend fun getTransactionStatus(txId: String) = getSyncOrNull(txId)
override suspend fun setTransactionStatus(txId: String, status: ExchangeAnalyticsStatus) = store(txId, status)
}

View file

@ -0,0 +1,18 @@
package com.tangem.datasource.local.swaptx
/**
* Runtime cache for storing swap transactions statuses sent to analytics
*/
interface SwapTransactionStatusStore {
suspend fun getTransactionStatus(txId: String): ExchangeAnalyticsStatus?
suspend fun setTransactionStatus(txId: String, status: ExchangeAnalyticsStatus)
}
enum class ExchangeAnalyticsStatus(val value: String) {
InProgress("In Progress"),
Done("Done"),
Fail("Fail"),
KYC("KYC"),
Refunded("Refunded"),
}

View file

@ -7,26 +7,16 @@ class TokenExchangeAnalyticsEvent(
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("Token", event, params, null) {
class CexTx(token: String) : TokenScreenAnalyticsEvent(
event = "Notice - ChangeNow Swap",
class CexTxStatusOpened(token: String) : TokenScreenAnalyticsEvent(
event = "ChangeNow Status Opened",
params = mapOf("Token" to token),
)
class CexTxOpened(token: String, status: String) : TokenScreenAnalyticsEvent(
event = "ChangeNow Swap Opened",
class CexTxStatusChanged(token: String, status: String) : TokenScreenAnalyticsEvent(
event = "ChangeNow Status",
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"),

View file

@ -50,6 +50,7 @@ dependencies {
implementation(projects.core.utils)
implementation(projects.core.analytics)
implementation(projects.core.analytics.models)
implementation(projects.core.datasource)
/** Domain modules */
implementation(projects.domain.appCurrency)

View file

@ -95,7 +95,7 @@ internal class TokenDetailsSwapTransactionsStateConverter(
fromCryptoSymbol = fromCurrency.currency.symbol,
fromFiatAmount = getFiatAmount(fromFiatAmount),
fromCurrencyIcon = iconStateConverter.convert(fromCurrency),
onClick = { clickIntents.onSwapTransactionClick(transaction.txId, transaction.status?.status) },
onClick = { clickIntents.onSwapTransactionClick(transaction.txId) },
onGoToProviderClick = { url ->
analyticsEventsHandlerProvider().send(
TokenExchangeAnalyticsEvent.GoToProviderStatus(cryptoCurrency.symbol),
@ -130,9 +130,6 @@ internal class TokenDetailsSwapTransactionsStateConverter(
if (txUrl == null) return null
return when (status) {
ExchangeStatus.Failed -> {
analyticsEventsHandlerProvider().send(
TokenExchangeAnalyticsEvent.Fail(cryptoCurrency.symbol),
)
ExchangeStatusNotifications.Failed {
analyticsEventsHandlerProvider().send(
TokenExchangeAnalyticsEvent.GoToProviderFail(cryptoCurrency.symbol),
@ -141,9 +138,6 @@ internal class TokenDetailsSwapTransactionsStateConverter(
}
}
ExchangeStatus.Verifying -> {
analyticsEventsHandlerProvider().send(
TokenExchangeAnalyticsEvent.Verification(cryptoCurrency.symbol),
)
ExchangeStatusNotifications.NeedVerification {
analyticsEventsHandlerProvider().send(
TokenExchangeAnalyticsEvent.GoToProviderKYC(cryptoCurrency.symbol),

View file

@ -3,10 +3,13 @@ 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.datasource.local.swaptx.ExchangeAnalyticsStatus
import com.tangem.datasource.local.swaptx.SwapTransactionStatusStore
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.tokens.models.analytics.TokenExchangeAnalyticsEvent
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.feature.swap.domain.SwapRepository
@ -33,6 +36,7 @@ internal class ExchangeStatusFactory(
private val swapRepository: SwapRepository,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
private val swapTransactionStatusStore: SwapTransactionStatusStore,
private val dispatchers: CoroutineDispatcherProvider,
private val clickIntents: TokenDetailsClickIntents,
private val appCurrencyProvider: Provider<AppCurrency>,
@ -90,10 +94,26 @@ internal class ExchangeStatusFactory(
return swapRepository.getExchangeStatus(txId)
.fold(
ifLeft = { null },
ifRight = { it },
ifRight = {
sendStatusUpdateAnalytics(it)
it
},
)
}
private suspend fun sendStatusUpdateAnalytics(statusModel: ExchangeStatusModel) {
val txId = statusModel.txId ?: return
val status = toAnalyticStatus(statusModel.status) ?: return
val savedStatus = swapTransactionStatusStore.getTransactionStatus(txId)
if (savedStatus != status) {
analyticsEventsHandlerProvider().send(
TokenExchangeAnalyticsEvent.CexTxStatusChanged(cryptoCurrency.symbol, status.value),
)
swapTransactionStatusStore.setTransactionStatus(txId, status)
}
}
private fun getExchangeStatusState(
savedTransactions: List<SavedSwapTransactionListModel>?,
cryptoCurrencyStatusList: List<CryptoCurrencyStatus>,
@ -123,4 +143,20 @@ internal class ExchangeStatusFactory(
this
}
}
private fun toAnalyticStatus(status: ExchangeStatus?): ExchangeAnalyticsStatus? {
return when (status) {
ExchangeStatus.New,
ExchangeStatus.Waiting,
ExchangeStatus.Sending,
ExchangeStatus.Confirming,
ExchangeStatus.Exchanging,
-> ExchangeAnalyticsStatus.InProgress
ExchangeStatus.Verifying -> ExchangeAnalyticsStatus.KYC
ExchangeStatus.Failed -> ExchangeAnalyticsStatus.Fail
ExchangeStatus.Finished -> ExchangeAnalyticsStatus.Done
ExchangeStatus.Refunded -> ExchangeAnalyticsStatus.Refunded
else -> null
}
}
}

View file

@ -2,7 +2,6 @@ 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 {
@ -40,7 +39,7 @@ interface TokenDetailsClickIntents {
fun onCloseRentInfoNotification()
fun onSwapTransactionClick(txId: String, status: ExchangeStatus?)
fun onSwapTransactionClick(txId: String)
fun onGoToProviderClick(url: String)
}

View file

@ -13,6 +13,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.datasource.local.swaptx.SwapTransactionStatusStore
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
@ -36,11 +37,9 @@ 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
@ -77,6 +76,7 @@ internal class TokenDetailsViewModel @Inject constructor(
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
private val swapRepository: SwapRepository,
private val swapTransactionRepository: SwapTransactionRepository,
private val swapTransactionStatusStore: SwapTransactionStatusStore,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val reduxStateHolder: ReduxStateHolder,
private val analyticsEventsHandler: AnalyticsEventHandler,
@ -116,6 +116,7 @@ internal class TokenDetailsViewModel @Inject constructor(
swapRepository = swapRepository,
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
getMultiCryptoCurrencyStatusUseCase = getMultiCryptoCurrencyStatusUseCase,
swapTransactionStatusStore = swapTransactionStatusStore,
dispatchers = dispatchers,
clickIntents = this,
appCurrencyProvider = Provider { selectedAppCurrencyFlow.value },
@ -215,9 +216,6 @@ internal class TokenDetailsViewModel @Inject constructor(
swapTxStatusTaskScheduler.cancelTask()
exchangeStatusFactory.invoke()
.onEach { swapTxs ->
if (swapTxs.isNotEmpty()) {
analyticsEventsHandler.send(TokenExchangeAnalyticsEvent.CexTx(cryptoCurrency.symbol))
}
swapTxStatusTaskScheduler.scheduleTask(
viewModelScope,
PeriodicTask(
@ -539,20 +537,9 @@ internal class TokenDetailsViewModel @Inject constructor(
uiState = stateFactory.getStateWithRemovedRentNotification()
}
override fun onSwapTransactionClick(txId: String, status: ExchangeStatus?) {
override fun onSwapTransactionClick(txId: String) {
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
}
analyticsEventsHandler.send(TokenExchangeAnalyticsEvent.CexTxStatusOpened(cryptoCurrency.symbol))
uiState = stateFactory.getStateWithExchangeStatusBottomSheet(swapTxState)
}