Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-16 16:43:08 +08:00
parent 5de74d024d
commit b1c2f8d6e3
10 changed files with 158 additions and 54 deletions

View file

@ -3,25 +3,52 @@ package com.tangem.feature.wallet.presentation.wallet.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
sealed class WalletScreenAnalyticsEvent(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("Main Screen", event, params) {
sealed class WalletScreenAnalyticsEvent {
object ScreenOpened : WalletScreenAnalyticsEvent("Screen opened")
object WalletSwipe : WalletScreenAnalyticsEvent("Wallet Swipe")
sealed class Basic(
event: String,
params: Map<String, String> = mapOf(),
error: Throwable? = null,
) : AnalyticsEvent(category = "Basic", event = event, params = params, error = error) {
class EnableBiometrics(state: AnalyticsParam.OnOffState) : WalletScreenAnalyticsEvent(
event = "Enable Biometric",
params = mapOf("State" to state.value),
)
object WalletOpened : Basic(event = "Wallet Opened")
class NoticeRateAppButton(result: AnalyticsParam.RateApp) : WalletScreenAnalyticsEvent(
event = "Notice - Rate The App Button Tapped",
params = mapOf("Result" to result.value),
)
class CardWasScanned(source: AnalyticsParam.ScannedFrom) : Basic(
event = "Card Was Scanned",
params = mapOf(
AnalyticsParam.SOURCE to source.value,
),
)
object NoticeBackupYourWalletTapped : WalletScreenAnalyticsEvent("Notice - Backup Your Wallet Tapped")
object NoticeScanYourCardTapped : WalletScreenAnalyticsEvent("Notice - Scan Your Card Tapped")
object NoticeWalletLocked : WalletScreenAnalyticsEvent("Notice - Wallet Locked")
class BalanceLoaded(balance: AnalyticsParam.CardBalanceState) : Basic(
event = "Balance Loaded",
params = mapOf(
AnalyticsParam.BALANCE to balance.value,
),
)
}
sealed class MainScreen(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(category = "Main Screen", event = event, params = params) {
object ScreenOpened : MainScreen(event = "Screen opened")
object WalletSwipe : MainScreen(event = "Wallet Swipe")
class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen(
event = "Enable Biometric",
params = mapOf("State" to state.value),
)
class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen(
event = "Notice - Rate The App Button Tapped",
params = mapOf("Result" to result.value),
)
object NoticeBackupYourWalletTapped : MainScreen(event = "Notice - Backup Your Wallet Tapped")
object NoticeScanYourCardTapped : MainScreen(event = "Notice - Scan Your Card Tapped")
object NoticeWalletLocked : MainScreen(event = "Notice - Wallet Locked")
object WalletUnlockTapped : MainScreen(event = "Button - Wallet Unlock Tapped")
}
}

View file

@ -24,14 +24,10 @@ internal class ScrollOffsetCollector(
private val isAutoScroll: State<Boolean>,
) : FlowCollector<List<LazyListItemInfo>> {
private val LazyListItemInfo.halfItemSize get() = size.div(other = 2)
private val LazyListItemInfo.halfItemSize
get() = size.div(other = 2)
private var currentIndex = walletsListConfig.selectedWalletIndex
set(value) {
if (field != value) {
field = value
}
}
override suspend fun emit(value: List<LazyListItemInfo>) {
// Auto scroll must not change wallet
@ -46,11 +42,14 @@ internal class ScrollOffsetCollector(
val lastItem = value.lastOrNull() ?: return
if (abs(firstItem.offset) > firstItem.halfItemSize) {
val newIndex = firstItem.index + 1
currentIndex = newIndex
walletsListConfig.onWalletChange(newIndex)
onWalletChange(newIndex = firstItem.index + 1)
} else if (abs(lastItem.offset) > lastItem.halfItemSize) {
val newIndex = lastItem.index - 1
onWalletChange(newIndex = lastItem.index - 1)
}
}
private fun onWalletChange(newIndex: Int) {
if (currentIndex != newIndex) {
currentIndex = newIndex
walletsListConfig.onWalletChange(newIndex)
}

View file

@ -77,6 +77,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.math.BigDecimal
import javax.inject.Inject
import kotlin.properties.Delegates
@ -185,7 +186,7 @@ internal class WalletViewModel @Inject constructor(
)
override fun onCreate(owner: LifecycleOwner) {
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.ScreenOpened)
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpened)
viewModelScope.launch(dispatchers.main) {
delay(timeMillis = 1_800)
@ -301,7 +302,8 @@ internal class WalletViewModel @Inject constructor(
override fun onGenerateMissedAddressesClick(missedAddressCurrencies: List<CryptoCurrency>) {
val state = uiState as? WalletState.ContentState ?: return
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.NoticeScanYourCardTapped)
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Main))
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.NoticeScanYourCardTapped)
viewModelScope.launch(dispatchers.io) {
val userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex)
@ -401,6 +403,9 @@ internal class WalletViewModel @Inject constructor(
override fun onScanToUnlockWalletClick() {
val state = uiState as? WalletState.ContentState ?: return
analyticsEventsHandler.send(event = WalletScreenAnalyticsEvent.MainScreen.WalletUnlockTapped)
val lockedWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex)
viewModelScope.launch(dispatchers.io) {
@ -419,7 +424,7 @@ internal class WalletViewModel @Inject constructor(
override fun onDetailsClick() = router.openDetailsScreen()
override fun onBackupCardClick() {
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.NoticeBackupYourWalletTapped)
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.NoticeBackupYourWalletTapped)
router.openOnboardingScreen()
}
@ -433,7 +438,9 @@ internal class WalletViewModel @Inject constructor(
}
override fun onLikeAppClick() {
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.NoticeRateAppButton(AnalyticsParam.RateApp.Liked))
analyticsEventsHandler.send(
WalletScreenAnalyticsEvent.MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Liked),
)
uiState = stateFactory.getStateAndTriggerEvent(
state = uiState,
event = WalletEvent.RateApp(
@ -448,7 +455,9 @@ internal class WalletViewModel @Inject constructor(
}
override fun onDislikeAppClick() {
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.NoticeRateAppButton(AnalyticsParam.RateApp.Disliked))
analyticsEventsHandler.send(
WalletScreenAnalyticsEvent.MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Disliked),
)
viewModelScope.launch(dispatchers.main) {
neverToSuggestRateAppUseCase()
@ -457,19 +466,20 @@ internal class WalletViewModel @Inject constructor(
}
override fun onCloseRateAppNotificationClick() {
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.NoticeRateAppButton(AnalyticsParam.RateApp.Closed))
analyticsEventsHandler.send(
WalletScreenAnalyticsEvent.MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Closed),
)
viewModelScope.launch(dispatchers.main) {
remindToRateAppLaterUseCase()
}
}
override fun onWalletChange(index: Int) {
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.WalletSwipe)
val state = uiState as? WalletState.ContentState ?: return
if (state.walletsListConfig.selectedWalletIndex == index) return
changeCardAnalyticsContextUseCase(scanResponse = getWallet(index).scanResponse)
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.WalletSwipe)
// Reset the job to avoid a redundant state updating
onWalletChangeJobHolder.update(null)
@ -769,7 +779,7 @@ internal class WalletViewModel @Inject constructor(
override fun onUnlockWalletClick() {
val state = uiState as? WalletState.ContentState ?: return
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.NoticeWalletLocked)
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.NoticeWalletLocked)
viewModelScope.launch(dispatchers.main) {
unlockWalletsUseCase(
@ -992,7 +1002,10 @@ internal class WalletViewModel @Inject constructor(
.onEach { maybeTokenList ->
uiState = stateFactory.getStateByTokensList(maybeTokenList.getTokenListWithWallet(wallet))
maybeTokenList.onRight { checkMultiWalletWithFunds(it) }
maybeTokenList.onRight {
analyticsEventsHandler.sendBalanceLoadedEvent(fiatBalance = it.totalFiatBalance)
checkMultiWalletWithFunds(it)
}
updateNotifications(
index = walletIndex,
@ -1095,6 +1108,17 @@ internal class WalletViewModel @Inject constructor(
uiState = stateFactory.getSingleCurrencyLoadedBalanceState(maybeCryptoCurrencyStatus)
maybeCryptoCurrencyStatus.onRight { status ->
val fiatAmount = status.value.fiatAmount
analyticsEventsHandler.send(
event = WalletScreenAnalyticsEvent.Basic.BalanceLoaded(
balance = when {
fiatAmount == null -> AnalyticsParam.CardBalanceState.BlockchainError
fiatAmount.isZero() -> AnalyticsParam.CardBalanceState.Empty
else -> AnalyticsParam.CardBalanceState.Full
},
),
)
singleWalletCryptoCurrencyStatus = status
if (status.value.amount?.isZero() == false) {
@ -1123,7 +1147,10 @@ internal class WalletViewModel @Inject constructor(
.onEach { maybeTokenList ->
uiState = stateFactory.getStateByTokensList(maybeTokenList.getTokenListWithWallet(wallet))
maybeTokenList.onRight { checkMultiWalletWithFunds(it) }
maybeTokenList.onRight { tokenList ->
analyticsEventsHandler.sendBalanceLoadedEvent(tokenList.totalFiatBalance)
checkMultiWalletWithFunds(tokenList)
}
updateNotifications(
index = walletIndex,
@ -1135,6 +1162,26 @@ internal class WalletViewModel @Inject constructor(
.saveIn(tokensJobHolder)
}
private fun AnalyticsEventHandler.sendBalanceLoadedEvent(fiatBalance: TokenList.FiatBalance) {
val cardBalanceState = when (fiatBalance) {
is TokenList.FiatBalance.Failed -> AnalyticsParam.CardBalanceState.BlockchainError
is TokenList.FiatBalance.Loaded -> {
if (fiatBalance.amount > BigDecimal.ZERO) {
AnalyticsParam.CardBalanceState.Full
} else if (fiatBalance.amount.isZero()) {
AnalyticsParam.CardBalanceState.Empty
} else {
null
}
}
else -> null
}
cardBalanceState?.let {
send(event = WalletScreenAnalyticsEvent.Basic.BalanceLoaded(balance = it))
}
}
private fun updateTxHistory(userWalletId: UserWalletId, currency: CryptoCurrency, refresh: Boolean) {
viewModelScope.launch(dispatchers.io) {
val txHistoryItemsCountEither = txHistoryItemsCountUseCase(