diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt deleted file mode 100644 index 21f5f23180..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.tap.common.analytics.events - -import com.tangem.core.analytics.models.AnalyticsEvent - -/** -[REDACTED_AUTHOR] - */ -sealed class SignIn( - event: String, - params: Map = emptyMap(), -) : AnalyticsEvent("Sign In", event, params) { - - class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In") - class ButtonCardSignIn : SignIn(event = "Button - Card Sign In") -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt index 54a235ba71..903f85a2e3 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt @@ -8,6 +8,7 @@ import arrow.core.right import com.tangem.common.* import com.tangem.common.core.TangemSdkError import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.utils.TrackingContextProxy import com.tangem.datasource.local.preferences.AppPreferencesStore @@ -19,10 +20,7 @@ import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.UserWalletsListRepository.LockMethod import com.tangem.domain.common.wallets.error.* import com.tangem.domain.hotwallet.repository.HotWalletRepository -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.models.wallet.isImported -import com.tangem.domain.models.wallet.isLocked +import com.tangem.domain.models.wallet.* import com.tangem.domain.wallets.R import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents import com.tangem.domain.wallets.builder.UserWalletIdBuilder @@ -261,7 +259,7 @@ internal class DefaultUserWalletsListRepository( when (unlockMethod) { UserWalletsListRepository.UnlockMethod.Biometric -> { unlockAllWallets().bind() - trackSignInEvent(userWallet, Basic.SignedIn.SignInType.Biometric) + trackSignInEvent(userWallet, AnalyticsParam.SignInType.Biometric) select(userWalletId) } UserWalletsListRepository.UnlockMethod.AccessCode -> { @@ -292,7 +290,7 @@ internal class DefaultUserWalletsListRepository( sensitiveInformationRepository.getAll(listOf(encryptionKey)) .doOnSuccess { sensitiveInfo -> updateWallets { it?.updateWith(sensitiveInfo) } - trackSignInEvent(userWallet, Basic.SignedIn.SignInType.AccessCode) + trackSignInEvent(userWallet, AnalyticsParam.SignInType.AccessCode) } .doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) } } @@ -330,7 +328,7 @@ internal class DefaultUserWalletsListRepository( walletIdToDerivedKeys = mapOf(userWallet.walletId to scanResponse.derivedKeys), ) } - trackSignInEvent(userWallet, Basic.SignedIn.SignInType.Card) + trackSignInEvent(userWallet, AnalyticsParam.SignInType.Card) } .doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) } } @@ -373,7 +371,7 @@ internal class DefaultUserWalletsListRepository( .doOnSuccess { sensitiveInfo -> updateWallets { wallets -> wallets?.updateWith(sensitiveInfo) } selectedUserWallet.value?.let { - trackSignInEvent(it, Basic.SignedIn.SignInType.Biometric) + trackSignInEvent(it, AnalyticsParam.SignInType.Biometric) } } .doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) } @@ -602,18 +600,14 @@ internal class DefaultUserWalletsListRepository( return lastOrNull() } - private fun trackSignInEvent(userWallet: UserWallet, type: Basic.SignedIn.SignInType) { + private fun trackSignInEvent(userWallet: UserWallet, type: AnalyticsParam.SignInType) { trackingContextProxy.addContext(userWallet) - val isBackedUp = when (userWallet) { - is UserWallet.Cold -> userWallet.scanResponse.card.backupStatus?.isActive == true - is UserWallet.Hot -> userWallet.backedUp - } analyticsEventHandler.send( event = Basic.SignedIn( signInType = type, walletsCount = userWallets.value?.size ?: 0, isImported = userWallet.isImported(), - hasBackup = isBackedUp, + isBackedUp = userWallet.isBackedUpForAnalytics(), ), ) } diff --git a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt index f90ef24ac4..2cdabfbc36 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt @@ -14,6 +14,7 @@ import com.tangem.common.routing.entity.InitScreenLaunchMode import com.tangem.common.ui.notifications.NotificationId import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsExceptionHandler +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.ExceptionAnalyticsEvent import com.tangem.core.analytics.utils.TrackingContextProxy @@ -30,7 +31,7 @@ import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.isBackedUpForAnalytics import com.tangem.domain.models.wallet.isImported import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.notifications.repository.NotificationsRepository @@ -360,16 +361,12 @@ internal class DefaultRoutingComponent @AssistedInject constructor( val userWallets = userWalletsListRepository.userWalletsSync() val selectedWallet = userWalletsListRepository.selectedUserWalletSync() ?: return trackingContextProxy.addContext(selectedWallet) - val isBackedUp = when (selectedWallet) { - is UserWallet.Cold -> selectedWallet.scanResponse.card.backupStatus?.isActive == true - is UserWallet.Hot -> selectedWallet.backedUp - } analyticsEventHandler.send( event = Basic.SignedIn( - signInType = Basic.SignedIn.SignInType.NoSecurity, + signInType = AnalyticsParam.SignInType.NoSecurity, walletsCount = userWallets.size, isImported = selectedWallet.isImported(), - hasBackup = isBackedUp, + isBackedUp = selectedWallet.isBackedUpForAnalytics(), ), ) } diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index d6b77eb6ba..75b448a196 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -239,6 +239,13 @@ sealed class AnalyticsParam { MobileWallet("Mobile Wallet"), } + enum class SignInType(val value: String) { + Card("Card"), + Biometric("Biometric"), + NoSecurity("No Security"), + AccessCode("Access Code"), + } + companion object Key { const val BLOCKCHAIN = "Blockchain" const val TOKEN_PARAM = "Token" @@ -297,6 +304,11 @@ sealed class AnalyticsParam { const val REFERRAL_ID = "Referral_ID" const val SEARCHED = "Searched" const val RATE_TYPE = "Rate Type" + const val SIGN_IN_TYPE = "Sign in type" + const val WALLETS_COUNT = "Wallets Count" + const val WALLET_TYPE = "Wallet Type" + const val BACKUPED = "Backuped" + const val MEMO = "Memo" } } diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt index 0e916a0e2d..f370340d57 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt @@ -2,65 +2,41 @@ package com.tangem.core.analytics.models sealed class Basic( event: String, - params: Map = mapOf(), -) : AnalyticsEvent("Basic", event, params) { + params: Map = emptyMap(), +) : AnalyticsEvent(category = "Basic", event = event, params = params) { + /** + * Tracks card scanning from specific entry points (Introduction, Main, My Wallets, Sign In). + * The originating screen is reported via the [AnalyticsParam.SOURCE] parameter. + */ class CardWasScanned( source: AnalyticsParam.ScreensSources, ) : Basic( event = "Card Was Scanned", params = mapOf( - AnalyticsParam.Key.SOURCE to source.value, + AnalyticsParam.SOURCE to source.value, ), - ) - - class SignedInLegacy( - currency: AnalyticsParam.WalletType, - batch: String, - signInType: SignInType, - walletsCount: String, - isImported: Boolean, - hasBackup: Boolean?, - ) : Basic( - event = "Signed in", - params = buildMap { - put(AnalyticsParam.Key.CURRENCY, currency.value) - put(AnalyticsParam.Key.BATCH, batch) - put("Wallet Type", if (isImported) "Seed Phrase" else "Seedless") - 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 - } - } + ), CriticalEvent + /** + * Tracks any sign-in into a wallet (card scan, FaceID, or wallet switch). + * Counted as a single sign-in per session — subsequent card scans within the same session are ignored. + */ class SignedIn( - signInType: SignInType, + signInType: AnalyticsParam.SignInType, walletsCount: Int, isImported: Boolean, - hasBackup: Boolean?, + isBackedUp: Boolean, ) : Basic( event = "Signed in", params = buildMap { - put("Sign in type", signInType.value) - put("Wallets Count", walletsCount.toString()) - put("Wallet Type", if (isImported) "Seed Phrase" else "Seedless") - if (hasBackup != null) { - put("Backuped", if (hasBackup) "Yes" else "No") - } + put(AnalyticsParam.SIGN_IN_TYPE, signInType.value) + put(AnalyticsParam.WALLETS_COUNT, walletsCount.toString()) + put(AnalyticsParam.WALLET_TYPE, if (isImported) "Seed Phrase" else "Seedless") + put(AnalyticsParam.BACKUPED, if (isBackedUp) "Yes" else "No") }, - ) { - enum class SignInType(val value: String) { - Card("Card"), - Biometric("Biometric"), - NoSecurity("No Security"), - AccessCode("Access Code"), - } + ), CriticalEvent, OneTimePerSessionEvent { + override val oneTimeEventId: String = id } class ButtonBuy( @@ -68,39 +44,46 @@ sealed class Basic( ) : Basic( event = "Button - Buy", params = buildMap { - put(AnalyticsParam.Key.SOURCE, source.value) + put(AnalyticsParam.SOURCE, source.value) }, ) - class ToppedUp(userWalletId: String, currency: AnalyticsParam.WalletType) : + /** + * Tracks the first time a user wallet is topped up. Sent once per wallet, when the balance + * transitions from zero to positive (Total Balance for multi-currency wallets, or Balance for Note). + * A wallet scanned with a non-zero balance does not count as a top-up — the event must be sent + * only after all tokens have finished loading. + */ + class ToppedUp(userWalletId: String, walletType: AnalyticsParam.WalletType) : Basic( event = "Topped up", - params = mapOf(AnalyticsParam.Key.CURRENCY to currency.value), + params = mapOf(AnalyticsParam.CURRENCY to walletType.value), ), - OneTimeAnalyticsEvent { + OneTimeAnalyticsEvent, AppsFlyerIncludedEvent, CriticalEvent { override val oneTimeEventId: String = id + userWalletId } + /** + * Tracks transaction submission from various screens (Send, Swap, WalletConnect, Sell, Approve, Staking). + */ class TransactionSent(sentFrom: AnalyticsParam.TxSentFrom, memoType: MemoType) : Basic( event = "Transaction sent", params = buildMap { - this[AnalyticsParam.Key.SOURCE] = sentFrom.value + put(AnalyticsParam.SOURCE, sentFrom.value) if (sentFrom is AnalyticsParam.TxData) { - this[AnalyticsParam.Key.BLOCKCHAIN] = sentFrom.blockchain - this[AnalyticsParam.Key.TOKEN_PARAM] = sentFrom.token - sentFrom.feeType?.value?.let { - this[AnalyticsParam.Key.FEE_TYPE] = it - } - this[AnalyticsParam.Key.FEE_TOKEN] = sentFrom.feeToken + put(AnalyticsParam.BLOCKCHAIN, sentFrom.blockchain) + put(AnalyticsParam.TOKEN_PARAM, sentFrom.token) + sentFrom.feeType?.value?.let { put(AnalyticsParam.FEE_TYPE, it) } + put(AnalyticsParam.FEE_TOKEN, sentFrom.feeToken) } if (sentFrom is AnalyticsParam.TxSentFrom.Approve) { - this[AnalyticsParam.Key.PERMISSION_TYPE] = sentFrom.permissionType + put(AnalyticsParam.PERMISSION_TYPE, sentFrom.permissionType) } - this["Memo"] = memoType.name + put(AnalyticsParam.MEMO, memoType.name) }, - ), AppsFlyerIncludedEvent { + ), AppsFlyerIncludedEvent, CriticalEvent { enum class MemoType { Empty, Full, Null } @@ -110,31 +93,33 @@ sealed class Basic( } } + /** + * Tracks the user invoking the "Request Support" email flow from various screens of the app. + */ class ButtonSupport(source: AnalyticsParam.ScreensSources) : Basic( event = "Request Support", params = mapOf( - AnalyticsParam.Key.SOURCE to source.value, + AnalyticsParam.SOURCE to source.value, + ), + ), CriticalEvent + + /** + * Tracks loading of the user's total balance after sign-in. Reports whether the balance is + * empty, has funds, failed to load, or could not be returned because of a custom token. + */ + class BalanceLoaded(balance: AnalyticsParam.CardBalanceState, tokensCount: Int?) : Basic( + event = "Balance Loaded", + params = buildMap { + put(AnalyticsParam.BALANCE, balance.value) + tokensCount?.let { put(AnalyticsParam.TOKENS_COUNT, it.toString()) } + }, + ), AppsFlyerIncludedEvent, CriticalEvent + + class TokenBalance(balance: AnalyticsParam.EmptyFull, token: String) : Basic( + event = "Token Balance", + params = mapOf( + AnalyticsParam.STATE to balance.value, + AnalyticsParam.TOKEN_PARAM to token, ), ) - - class BiometryFailed( - source: AnalyticsParam.ScreensSources, - reason: BiometricFailReason, - ) : Basic( - event = "Biometry Failed", - params = mapOf( - AnalyticsParam.Key.SOURCE to source.value, - "Reason" to reason.value, - ), - ) { - sealed class BiometricFailReason(val value: String) { - data object AuthenticationLockout : BiometricFailReason("BiometricsAuthenticationLockout") - data object AuthenticationLockoutPermanent : BiometricFailReason("BiometricsAuthenticationLockoutPermanent") - data object BiometricsAuthenticationDisabled : BiometricFailReason("BiometricsAuthenticationDisabled") - data object AllKeysInvalidated : BiometricFailReason("AllKeysInvalidated") - data object AuthenticationCancelled : BiometricFailReason("AuthenticationCancelled") - data object AuthenticationAlreadyInProgress : BiometricFailReason("AuthenticationAlreadyInProgress") - data class Other(val reason: String) : BiometricFailReason(reason) - } - } } \ No newline at end of file diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/CriticalEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/CriticalEvent.kt new file mode 100644 index 0000000000..ff4cb0907b --- /dev/null +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/CriticalEvent.kt @@ -0,0 +1,8 @@ +package com.tangem.core.analytics.models + +/** + * Marker interface for analytics events that require special attention. + * + * Implemented by events listed in the analytics specification (events.csv). + */ +interface CriticalEvent \ No newline at end of file diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/SignIn.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/SignIn.kt index f62e70074f..d28f389f08 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/SignIn.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/SignIn.kt @@ -2,20 +2,22 @@ package com.tangem.core.analytics.models.event import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.CriticalEvent sealed class SignIn( event: String, params: Map = emptyMap(), -) : AnalyticsEvent("Sign In", event, params) { +) : AnalyticsEvent(category = "Sign In", event = event, params = params) { - data class ScreenOpened( - val walletsCount: Int, - ) : SignIn( + /** + * Tracks the user landing on the app's sign-in screen when a saved card exists. + */ + class ScreenOpened(walletsCount: Int) : SignIn( event = "Sign In Screen Opened", params = mapOf( - "Wallets Count" to walletsCount.toString(), + AnalyticsParam.WALLETS_COUNT to walletsCount.toString(), ), - ) + ), CriticalEvent class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In") @@ -26,24 +28,17 @@ sealed class SignIn( ) : SignIn(event = "Error - Biometric Updated") class ButtonWallet( - signInType: SignInType, + signInType: AnalyticsParam.SignInType, walletsCount: Int, ) : SignIn( event = "Button - Wallet", params = buildMap { - put("Wallets Count", walletsCount.toString()) - put("Sign in type", signInType.value) + put(AnalyticsParam.WALLETS_COUNT, walletsCount.toString()) + put(AnalyticsParam.SIGN_IN_TYPE, signInType.value) }, - ) { - enum class SignInType(val value: String) { - Card("Card"), - Biometric("Biometric"), - NoSecurity("No Security"), - AccessCode("Access Code"), - } - } + ) - data class ButtonAddWallet( + class ButtonAddWallet( val sources: AnalyticsParam.ScreensSources, ) : SignIn( event = "Button - Add Wallet", diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/analytics/IntroductionProcess.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/analytics/IntroductionProcess.kt index 98114357c7..2b8b3299ae 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/analytics/IntroductionProcess.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/analytics/IntroductionProcess.kt @@ -2,6 +2,7 @@ package com.tangem.domain.card.analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.CriticalEvent import com.tangem.core.analytics.models.getReferralParams sealed class IntroductionProcess( @@ -9,11 +10,17 @@ sealed class IntroductionProcess( params: Map = emptyMap(), ) : AnalyticsEvent("Introduction Process", event, params) { - class ScreenOpened : IntroductionProcess("Introduction Process Screen Opened") + /** + * Tracks the user opening the Introduction Process screen. + */ + class ScreenOpened : IntroductionProcess("Introduction Process Screen Opened"), CriticalEvent class ButtonTokensList : IntroductionProcess("Button - Tokens List") class ButtonBuyCards : IntroductionProcess("Button - Buy Cards") class ButtonScanCardLegacy : IntroductionProcess("Button - Scan Card") + /** + * Tracks opening the Create Wallet introduction screen. + */ class CreateWalletIntroScreenOpened( referralId: String?, ) : IntroductionProcess( @@ -21,7 +28,7 @@ sealed class IntroductionProcess( params = buildMap { putAll(getReferralParams(referralId)) }, - ) + ), CriticalEvent class ButtonScanCard( val source: AnalyticsParam.ScreensSources, diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/wallet/UserWallet.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/wallet/UserWallet.kt index 976b735b4f..acc0333fe9 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/wallet/UserWallet.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/wallet/UserWallet.kt @@ -93,6 +93,13 @@ fun UserWallet.isImported(): Boolean { } } +fun UserWallet.isBackedUpForAnalytics(): Boolean { + return when (this) { + is UserWallet.Cold -> scanResponse.card.backupStatus?.isActive == true + is UserWallet.Hot -> backedUp + } +} + fun UserWallet.copy(name: String = this.name, walletId: UserWalletId = this.walletId): UserWallet = when (this) { is UserWallet.Cold -> this.copy(name = name, walletId = walletId) is UserWallet.Hot -> this.copy(name = name, walletId = walletId) diff --git a/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt b/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt index 3e871cd61e..4460856afc 100644 --- a/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt +++ b/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt @@ -8,8 +8,8 @@ import com.tangem.common.routing.AppRouter import com.tangem.common.routing.entity.InitScreenLaunchMode import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.analytics.models.Basic.SignedInLegacy -import com.tangem.core.analytics.models.Basic.SignedInLegacy.SignInType +import com.tangem.core.analytics.models.AnalyticsParam.SignInType +import com.tangem.core.analytics.models.Basic.SignedIn import com.tangem.core.decompose.di.GlobalUiMessageSender import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model @@ -20,9 +20,7 @@ import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.ui.message.dialog.Dialogs import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.analytics.IntroductionProcess -import com.tangem.domain.card.analytics.ParamCardCurrencyConverter import com.tangem.domain.card.analytics.Shop -import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.error.SaveWalletError @@ -34,18 +32,18 @@ import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions import com.tangem.domain.wallets.builder.ColdUserWalletBuilder import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase import com.tangem.domain.wallets.usecase.SaveWalletUseCase +import com.tangem.feature.referral.domain.ShouldShowMobileWalletPromoUseCase import com.tangem.features.home.api.HomeComponent import com.tangem.features.home.impl.ui.state.HomeUM import com.tangem.features.home.impl.ui.state.Stories import com.tangem.features.home.impl.ui.state.getRestrictedStories -import com.tangem.feature.referral.domain.ShouldShowMobileWalletPromoUseCase import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.Debouncer +import com.tangem.utils.logging.TangemLogger import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import com.tangem.utils.logging.TangemLogger import java.util.Locale import javax.inject.Inject @@ -218,19 +216,14 @@ internal class HomeModel @Inject constructor( } private suspend fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse, isImported: Boolean) { - val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver) - if (currency != null) { - analyticsEventHandler.send( - SignedInLegacy( - currency = currency, - batch = scanResponse.card.batchId, - signInType = SignInType.Card, - walletsCount = userWalletsListRepository.userWalletsSync().size.toString(), - isImported = isImported, - hasBackup = scanResponse.card.backupStatus?.isActive, - ), - ) - } + analyticsEventHandler.send( + SignedIn( + signInType = SignInType.Card, + walletsCount = userWalletsListRepository.userWalletsSync().size, + isImported = isImported, + isBackedUp = scanResponse.card.backupStatus?.isActive == true, + ), + ) } private fun setLoading(isLoading: Boolean) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt index 6bc6f97a27..7ee1dd5b1d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt @@ -243,10 +243,6 @@ internal class WalletModel @Inject constructor( } else { null } - val isBackedUp = when (selectedWallet) { - is UserWallet.Cold -> selectedWallet.scanResponse.card.backupStatus?.isActive == true - is UserWallet.Hot -> selectedWallet.backedUp - } val result = getAppThemeModeUseCase().firstOrNull() val theme = result?.getOrElse { AppThemeMode.FOLLOW_SYSTEM } ?: AppThemeMode.FOLLOW_SYSTEM val appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }.code @@ -254,7 +250,7 @@ internal class WalletModel @Inject constructor( WalletScreenAnalyticsEvent.MainScreen.ScreenOpened( hasMobileWallet = hasMobileWallet, accountsCount = accountsCount, - isBackedUp = isBackedUp, + isBackedUp = selectedWallet.isBackedUpForAnalytics(), theme = theme.value, isImported = selectedWallet.isImported(), referralId = appsFlyerStore.get()?.refcode, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt index 7c66a4acc4..ef664fa1f0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt @@ -1,19 +1,20 @@ package com.tangem.feature.wallet.child.wallet.model.intents import arrow.core.getOrElse -import com.tangem.utils.logging.TangemLogger import com.tangem.common.routing.AppRoute.* import com.tangem.common.routing.AppRouter import com.tangem.common.ui.notifications.NotificationId import com.tangem.common.ui.userwallet.handle import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic.ButtonSupport import com.tangem.core.analytics.models.event.AssetsDiscoveryAnalyticsEvent import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.navigation.review.ReviewManager import com.tangem.core.navigation.url.UrlOpener +import com.tangem.domain.assetsdiscovery.usecase.AcknowledgeAssetsDiscoveryCompletionUseCase import com.tangem.domain.card.SetCardWasScannedUseCase import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.feedback.GetWalletMetaInfoUseCase @@ -41,16 +42,15 @@ import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.Program import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.PromotionBannerClicked import com.tangem.domain.tokens.model.details.NavigationAction -import com.tangem.domain.assetsdiscovery.usecase.AcknowledgeAssetsDiscoveryCompletionUseCase import com.tangem.domain.wallets.usecase.* import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent -import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.logging.TangemLogger import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index 4462ad2539..f7e59102b3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -1,56 +1,14 @@ package com.tangem.feature.wallet.presentation.wallet.analytics import com.tangem.core.analytics.models.* -import com.tangem.domain.models.wallet.UserWalletId sealed class WalletScreenAnalyticsEvent { - sealed class Basic( - event: String, - params: Map = mapOf(), - ) : AnalyticsEvent(category = "Basic", event = event, params = params) { - - class WalletToppedUp(userWalletId: UserWalletId, walletType: AnalyticsParam.WalletType) : - Basic( - event = "Topped up", - params = mapOf(AnalyticsParam.CURRENCY to walletType.value), - ), - OneTimeAnalyticsEvent, AppsFlyerIncludedEvent { - - override val oneTimeEventId: String = id + userWalletId.stringValue - } - - class CardWasScanned(source: AnalyticsParam.ScreensSources) : Basic( - event = "Card Was Scanned", - params = mapOf( - AnalyticsParam.SOURCE to source.value, - ), - ) - - class BalanceLoaded(balance: AnalyticsParam.CardBalanceState, tokensCount: Int?) : Basic( - event = "Balance Loaded", - params = buildMap { - put(AnalyticsParam.BALANCE, balance.value) - tokensCount?.let { put(AnalyticsParam.TOKENS_COUNT, it.toString()) } - }, - ), AppsFlyerIncludedEvent - - class TokenBalance(balance: AnalyticsParam.EmptyFull, token: String) : Basic( - event = "Token Balance", - params = mapOf( - AnalyticsParam.STATE to balance.value, - AnalyticsParam.TOKEN_PARAM to token, - ), - ) - } - sealed class MainScreen( event: String, params: Map = mapOf(), ) : AnalyticsEvent(category = "Main Screen", event = event, params = params) { - class ScreenOpenedLegacy : MainScreen(event = "Screen opened") - data class ScreenOpened( private val hasMobileWallet: Boolean, private val accountsCount: Int?, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt index 0a6847ed30..ea8648688a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt @@ -8,6 +8,7 @@ import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.common.extensions.isZero import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.analytics.CheckIsWalletToppedUpUseCase @@ -18,7 +19,6 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.isMultiCurrency -import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider @@ -206,7 +206,12 @@ internal class TokenListAnalyticsSender @Inject constructor( AnalyticsParam.WalletType.SingleCurrency(currency.currency.name) } - analyticsEventHandler.send(Basic.WalletToppedUp(userWallet.walletId, walletType)) + analyticsEventHandler.send( + Basic.ToppedUp( + userWalletId = userWallet.walletId.stringValue, + walletType = walletType, + ), + ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/PrimaryCurrencySubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/PrimaryCurrencySubscriber.kt index 9e389948bd..063a5d7309 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/PrimaryCurrencySubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/PrimaryCurrencySubscriber.kt @@ -3,12 +3,12 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.common.extensions.isZero import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.Basic import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet -import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetPrimaryCurrencyTransformer import dagger.assisted.Assisted @@ -70,7 +70,7 @@ internal class PrimaryCurrencySubscriber @AssistedInject constructor( cardBalanceState?.let { balanceState -> // do not send tokens count for single currency wallet analyticsEventHandler.send( - event = WalletScreenAnalyticsEvent.Basic.BalanceLoaded( + event = Basic.BalanceLoaded( balance = balanceState, tokensCount = null, ), diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt index 0c671bec95..c40e38cd57 100644 --- a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt @@ -19,10 +19,7 @@ import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.error.SaveWalletError import com.tangem.domain.common.wallets.error.UnlockWalletError -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.models.wallet.isImported -import com.tangem.domain.models.wallet.isLocked +import com.tangem.domain.models.wallet.* import com.tangem.domain.settings.CanUseBiometryUseCase import com.tangem.domain.settings.HotWalletRestrictionManager import com.tangem.domain.wallets.builder.ColdUserWalletBuilder @@ -76,9 +73,9 @@ internal class WelcomeModel @Inject constructor( val userWallet = userWallets.first { it.walletId == walletId } trackingContextProxy.addContext(userWallet) val signInType = when { - !userWallet.isLocked -> SignIn.ButtonWallet.SignInType.NoSecurity - userWallet is UserWallet.Cold -> SignIn.ButtonWallet.SignInType.Card - else -> SignIn.ButtonWallet.SignInType.AccessCode + !userWallet.isLocked -> AnalyticsParam.SignInType.NoSecurity + userWallet is UserWallet.Cold -> AnalyticsParam.SignInType.Card + else -> AnalyticsParam.SignInType.AccessCode } analyticsEventHandler.send( event = SignIn.ButtonWallet( @@ -256,7 +253,7 @@ internal class WelcomeModel @Inject constructor( if (userWallet.isLocked.not()) { // If the wallet is not locked, we can proceed to the wallet screen directly userWalletsListRepository.select(userWallet.walletId) - trackSignInEvent(userWallet, Basic.SignedIn.SignInType.NoSecurity) + trackSignInEvent(userWallet, AnalyticsParam.SignInType.NoSecurity) router.replaceAll(AppRoute.Wallet) return@launch } @@ -317,19 +314,15 @@ internal class WelcomeModel @Inject constructor( } } - private suspend fun trackSignInEvent(userWallet: UserWallet, type: Basic.SignedIn.SignInType) { + private suspend fun trackSignInEvent(userWallet: UserWallet, type: AnalyticsParam.SignInType) { val walletsCount = userWalletsListRepository.userWalletsSync().size trackingContextProxy.addContext(userWallet) - val isBackedUp = when (userWallet) { - is UserWallet.Cold -> userWallet.scanResponse.card.backupStatus?.isActive == true - is UserWallet.Hot -> userWallet.backedUp - } analyticsEventHandler.send( event = Basic.SignedIn( signInType = type, walletsCount = walletsCount, isImported = userWallet.isImported(), - hasBackup = isBackedUp, + isBackedUp = userWallet.isBackedUpForAnalytics(), ), ) }