Updated on 2026-08-14
This commit is contained in:
parent
5de74d024d
commit
b1c2f8d6e3
10 changed files with 158 additions and 54 deletions
|
|
@ -32,14 +32,18 @@ sealed class Basic(
|
|||
batch: String,
|
||||
signInType: SignInType,
|
||||
walletsCount: String,
|
||||
hasBackup: Boolean?,
|
||||
) : Basic(
|
||||
event = "Signed in",
|
||||
params = mapOf(
|
||||
AnalyticsParam.CURRENCY to currency.value,
|
||||
AnalyticsParam.BATCH to batch,
|
||||
"Sign in type" to signInType.name,
|
||||
"Wallets Count" to walletsCount,
|
||||
),
|
||||
params = buildMap {
|
||||
put(AnalyticsParam.CURRENCY, currency.value)
|
||||
put(AnalyticsParam.BATCH, batch)
|
||||
put("Sign in type", signInType.name)
|
||||
put("Wallets Count", walletsCount)
|
||||
if (hasBackup != null) {
|
||||
put("Backuped", if (hasBackup) "Yes" else "No")
|
||||
}
|
||||
},
|
||||
) {
|
||||
enum class SignInType {
|
||||
Card, Biometric
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ package com.tangem.tap.common.redux.global
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
|
|
@ -182,6 +184,8 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
action.manager.selectedUserWallet
|
||||
.distinctUntilChanged()
|
||||
.onEach { userWallet ->
|
||||
Analytics.send(event = Basic.WalletOpened())
|
||||
|
||||
store.state.globalState.feedbackManager?.infoHolder?.let { infoHolder ->
|
||||
infoHolder.setCardInfo(data = userWallet.scanResponse)
|
||||
|
||||
|
|
|
|||
|
|
@ -30,12 +30,14 @@ internal class SaveWalletViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun saveWallet() {
|
||||
analyticsEventHandler.send(WalletScreenAnalyticsEvent.EnableBiometrics(AnalyticsParam.OnOffState.On))
|
||||
analyticsEventHandler.send(WalletScreenAnalyticsEvent.MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.On))
|
||||
store.dispatch(SaveWalletAction.Save)
|
||||
}
|
||||
|
||||
fun cancelOrClose() {
|
||||
analyticsEventHandler.send(WalletScreenAnalyticsEvent.EnableBiometrics(AnalyticsParam.OnOffState.Off))
|
||||
analyticsEventHandler.send(
|
||||
WalletScreenAnalyticsEvent.MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.Off),
|
||||
)
|
||||
store.dispatch(SaveWalletAction.Dismiss)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ internal class WalletViewModel @Inject constructor(
|
|||
batch = scanResponse.card.batchId,
|
||||
signInType = signInType,
|
||||
walletsCount = store.state.globalState.userWalletsListManager?.walletsCount.toString(),
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,15 @@ import com.tangem.common.doOnFailure
|
|||
import com.tangem.common.doOnResult
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.userwallets.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.unlockIfLockable
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
|
|
@ -88,6 +91,11 @@ internal class WelcomeMiddleware {
|
|||
store.dispatchWithMain(WelcomeAction.ProceedWithBiometrics.Error(error))
|
||||
}
|
||||
.doOnSuccess { selectedUserWallet ->
|
||||
sendSignedInAnalyticsEvent(
|
||||
scanResponse = selectedUserWallet.scanResponse,
|
||||
signInType = Basic.SignedIn.SignInType.Biometric,
|
||||
)
|
||||
|
||||
store.dispatchWithMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Biometric))
|
||||
store.dispatchWithMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithBiometrics.Success)
|
||||
|
|
@ -116,6 +124,8 @@ internal class WelcomeMiddleware {
|
|||
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Error(error))
|
||||
}
|
||||
.doOnSuccess {
|
||||
sendSignedInAnalyticsEvent(scanResponse = scanResponse, signInType = Basic.SignedIn.SignInType.Card)
|
||||
|
||||
store.dispatchWithMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Card))
|
||||
store.dispatchWithMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Success)
|
||||
|
|
@ -128,6 +138,24 @@ internal class WelcomeMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun sendSignedInAnalyticsEvent(scanResponse: ScanResponse, signInType: Basic.SignedIn.SignInType) {
|
||||
val currency = ParamCardCurrencyConverter().convert(
|
||||
value = scanResponse.cardTypesResolver,
|
||||
)
|
||||
|
||||
if (currency != null) {
|
||||
Analytics.send(
|
||||
event = Basic.SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = signInType,
|
||||
walletsCount = store.state.globalState.userWalletsListManager?.walletsCount.toString(),
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun disableUserWalletsSaving() {
|
||||
userWalletsListManager.clear()
|
||||
.flatMap { walletStoresManager.clear() }
|
||||
|
|
|
|||
|
|
@ -38,8 +38,4 @@ interface CardTypesResolver {
|
|||
fun isAttestationFailed(): Boolean
|
||||
|
||||
fun hasWalletSignedHashes(): Boolean
|
||||
|
||||
fun hasBackup(): Boolean
|
||||
|
||||
fun isBackupForbidden(): Boolean
|
||||
}
|
||||
|
|
@ -92,10 +92,6 @@ internal class TangemCardTypesResolver(
|
|||
}
|
||||
}
|
||||
|
||||
override fun hasBackup(): Boolean = card.backupStatus != CardDTO.BackupStatus.NoBackup
|
||||
|
||||
override fun isBackupForbidden(): Boolean = !(card.settings.isBackupAllowed || card.settings.isHDWalletAllowed)
|
||||
|
||||
private fun Blockchain.Companion.fromBlockchainName(blockchainName: String): Blockchain {
|
||||
// workaround for BSC (BNB) notes cards
|
||||
return when (blockchainName) {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue