diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index e5e4c697a6..d1dd0f06bb 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -8,10 +8,7 @@ sealed class AnalyticsParam { class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol) class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency) class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol) - class FiatCurrency( - fiatCurrency: com.tangem.tap.common.entities.FiatCurrency, - ) : CurrencyType(fiatCurrency.code) - + class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency) : CurrencyType(fiatCurrency.code) class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt index 3e961883b7..8270df641c 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt @@ -12,6 +12,10 @@ sealed class Portfolio( ) : AnalyticsEvent("Portfolio", event, params) { class Refreshed : Portfolio("Refreshed") + class ButtonManageTokens : Portfolio("Button - Manage Tokens") + class TokenTapped : Portfolio("Token is Tapped") + + class OrganizeTokens : Portfolio("Button - Organize Tokens") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt index ae186c4912..67cdd2c0bd 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt @@ -1,18 +1,26 @@ package com.tangem.tap.features.saveWallet.ui import androidx.lifecycle.ViewModel +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.features.saveWallet.redux.SaveWalletState import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog import com.tangem.tap.store +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import org.rekotlin.StoreSubscriber +import javax.inject.Inject -internal class SaveWalletViewModel : ViewModel(), StoreSubscriber { +@HiltViewModel +internal class SaveWalletViewModel @Inject constructor( + private val analyticsEventHandler: AnalyticsEventHandler, +) : ViewModel(), StoreSubscriber { private val stateInternal = MutableStateFlow(SaveWalletScreenState()) val state: StateFlow = stateInternal @@ -22,10 +30,12 @@ internal class SaveWalletViewModel : ViewModel(), StoreSubscriber Min + Normal.value -> Normal + Max.value -> Max + Fixed.value -> Fixed + else -> Fixed + } + } + } + } + + sealed class WalletCreationType(val value: String) { + object PrivateKey : WalletCreationType("Private key") + object NewSeed : WalletCreationType("New seed") + object SeedImport : WalletCreationType("Seed import") + } + + companion object Key { + const val BLOCKCHAIN = "blockchain" + const val TOKEN = "Token" + const val SOURCE = "Source" + const val BALANCE = "Balance" + const val BATCH = "Batch" + const val FEE_TYPE = "Fee Type" + const val PERMISSION_TYPE = "Permission Type" + const val PRODUCT_TYPE = "Product Type" + const val FIRMWARE = "Firmware" + const val CURRENCY = "Currency" + const val ERROR_DESCRIPTION = "Error Description" + const val ERROR_CODE = "Error Code" + const val ERROR_KEY = "Error Key" + const val CREATION_TYPE = "Creation type" + const val DAPP_NAME = "DApp Name" + const val DAPP_URL = "DApp Url" + const val METHOD_NAME = "Method Name" + const val VALIDATION = "Validation" + const val BLOCKCHAIN_EXCEPTION_HOST = "exception_host" + const val BLOCKCHAIN_SELECTED_HOST = "selected_host" + } +} \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 0e84af12c3..3f75bd4fc9 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -44,6 +44,8 @@ dependencies { implementation(projects.core.navigation) implementation(projects.core.ui) implementation(projects.core.utils) + implementation(projects.core.analytics) + implementation(projects.core.analytics.models) /** Domain modules */ implementation(projects.common) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt index e490a4a9b6..a972f62260 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt @@ -1,10 +1,10 @@ package com.tangem.feature.wallet.presentation.organizetokens -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.* import arrow.core.getOrElse import com.tangem.common.Provider +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.ApplyTokenListSortingUseCase @@ -13,6 +13,7 @@ import com.tangem.domain.tokens.ToggleTokenListGroupingUseCase import com.tangem.domain.tokens.ToggleTokenListSortingUseCase import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.wallet.presentation.organizetokens.analytics.PortfolioOrganizeTokensAnalyticsEvent import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState import com.tangem.feature.wallet.presentation.organizetokens.utils.CryptoCurrenciesIdsResolver @@ -35,9 +36,10 @@ internal class OrganizeTokensViewModel @Inject constructor( private val toggleTokenListSortingUseCase: ToggleTokenListSortingUseCase, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val analyticsEventsHandler: AnalyticsEventHandler, private val dispatchers: CoroutineDispatcherProvider, savedStateHandle: SavedStateHandle, -) : ViewModel(), OrganizeTokensIntents { +) : ViewModel(), DefaultLifecycleObserver, OrganizeTokensIntents { lateinit var router: InnerWalletRouter @@ -68,11 +70,17 @@ internal class OrganizeTokensViewModel @Inject constructor( val uiState: StateFlow = stateHolder.stateFlow + override fun onCreate(owner: LifecycleOwner) { + analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.ScreenOpened) + } + override fun onBackClick() { router.popBackStack() } override fun onSortClick() { + analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.ByBalance) + viewModelScope.launch(dispatchers.default) { val list = tokenList ?: return@launch @@ -87,6 +95,8 @@ internal class OrganizeTokensViewModel @Inject constructor( } override fun onGroupClick() { + analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.Group) + viewModelScope.launch(dispatchers.default) { val list = tokenList ?: return@launch @@ -107,11 +117,19 @@ internal class OrganizeTokensViewModel @Inject constructor( val listState = uiState.value.itemsState val resolver = CryptoCurrenciesIdsResolver() + val isGroupedByNetwork = listState is OrganizeTokensListState.GroupedByNetwork + val isSortedByBalance = uiState.value.header.isSortedByBalance + + sendAnalyticsEvent( + isGroupedByNetwork = isGroupedByNetwork, + isSortedByBalance = isSortedByBalance, + ) + val result = applyTokenListSortingUseCase( userWalletId = userWalletId, sortedTokensIds = resolver.resolve(listState, tokenList), - isGroupedByNetwork = listState is OrganizeTokensListState.GroupedByNetwork, - isSortedByBalance = uiState.value.header.isSortedByBalance, + isGroupedByNetwork = isGroupedByNetwork, + isSortedByBalance = isSortedByBalance, ) result.fold( @@ -127,6 +145,8 @@ internal class OrganizeTokensViewModel @Inject constructor( } override fun onCancelClick() { + analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.Cancel) + router.popBackStack() } @@ -166,4 +186,21 @@ internal class OrganizeTokensViewModel @Inject constructor( initialValue = AppCurrency.Default, ) } + + private fun sendAnalyticsEvent(isGroupedByNetwork: Boolean, isSortedByBalance: Boolean) { + analyticsEventsHandler.send( + PortfolioOrganizeTokensAnalyticsEvent.Apply( + grouping = if (isGroupedByNetwork) { + AnalyticsParam.OnOffState.On + } else { + AnalyticsParam.OnOffState.Off + }, + organizeSortType = if (isSortedByBalance) { + AnalyticsParam.OrganizeSortType.ByBalance + } else { + AnalyticsParam.OrganizeSortType.Manually + }, + ), + ) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/analytics/PortfolioOrganizeTokensAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/analytics/PortfolioOrganizeTokensAnalyticsEvent.kt new file mode 100644 index 0000000000..4d5ccf9b2c --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/analytics/PortfolioOrganizeTokensAnalyticsEvent.kt @@ -0,0 +1,29 @@ +package com.tangem.feature.wallet.presentation.organizetokens.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam + +sealed class PortfolioOrganizeTokensAnalyticsEvent( + event: String, + params: Map = mapOf(), +) : AnalyticsEvent("Portfolio / Organize Tokens", event, params) { + + object ScreenOpened : PortfolioOrganizeTokensAnalyticsEvent("Organize Tokens Screen Opened") + + object ByBalance : PortfolioOrganizeTokensAnalyticsEvent("Button - By Balance") + + object Group : PortfolioOrganizeTokensAnalyticsEvent("Button - Group") + + class Apply( + grouping: AnalyticsParam.OnOffState, + organizeSortType: AnalyticsParam.OrganizeSortType, + ) : PortfolioOrganizeTokensAnalyticsEvent( + "Button - Apply", + params = mapOf( + "Group" to grouping.value, + "Sort" to organizeSortType.value, + ), + ) + + object Cancel : PortfolioOrganizeTokensAnalyticsEvent("Button - Cancel") +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/PortfolioEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/PortfolioEvent.kt new file mode 100644 index 0000000000..85553a1445 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/PortfolioEvent.kt @@ -0,0 +1,17 @@ +package com.tangem.feature.wallet.presentation.wallet.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent + +sealed class PortfolioEvent( + event: String, + params: Map = mapOf(), +) : AnalyticsEvent("Portfolio", event, params) { + + object Refreshed : PortfolioEvent("Refreshed") + + object ButtonManageTokens : PortfolioEvent("Button - Manage Tokens") + + object TokenTapped : PortfolioEvent("Token is Tapped") + + object OrganizeTokens : PortfolioEvent("Button - Organize Tokens") +} \ No newline at end of file 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 new file mode 100644 index 0000000000..4eabe0f5b5 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -0,0 +1,28 @@ +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 = mapOf(), +) : AnalyticsEvent("Main Screen", event, params) { + + object ScreenOpened : WalletScreenAnalyticsEvent("Screen opened") + object WalletSwipe : WalletScreenAnalyticsEvent("Wallet Swipe") + + class EnableBiometrics(state: AnalyticsParam.OnOffState) : WalletScreenAnalyticsEvent( + event = "Enable Biometric", + params = mapOf("State" to state.value), + ) + + // TODO [REDACTED_JIRA] + class NoticeRateAppButton(result: AnalyticsParam.RateApp) : WalletScreenAnalyticsEvent( + event = "Notice - Rate The App Button Tapped", + params = mapOf("Result" to result.value), + ) + + object NoticeBackupYourWalletTapped : WalletScreenAnalyticsEvent("Notice - Backup Your Wallet Tapped") + object NoticeScanYourCardTapped : WalletScreenAnalyticsEvent("Notice - Scan Your Card Tapped") + object NoticeWalletLocked : WalletScreenAnalyticsEvent("Notice - Wallet Locked") +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index c225147e38..4565a8703d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -6,6 +6,7 @@ import arrow.core.getOrElse import com.tangem.common.Provider import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.navigation.AppScreen import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig @@ -37,6 +38,8 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.* import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.router.InnerWalletRouter +import com.tangem.feature.wallet.presentation.wallet.analytics.PortfolioEvent +import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.state.* import com.tangem.feature.wallet.presentation.wallet.state.components.WalletBottomSheetConfig import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState @@ -93,6 +96,7 @@ internal class WalletViewModel @Inject constructor( private val walletManagersFacade: WalletManagersFacade, private val reduxStateHolder: ReduxStateHolder, private val dispatchers: CoroutineDispatcherProvider, + private val analyticsEventsHandler: AnalyticsEventHandler, // endregion Parameters ) : ViewModel(), DefaultLifecycleObserver, WalletClickIntents { @@ -141,6 +145,8 @@ internal class WalletViewModel @Inject constructor( ) override fun onCreate(owner: LifecycleOwner) { + analyticsEventsHandler.send(WalletScreenAnalyticsEvent.ScreenOpened) + viewModelScope.launch(dispatchers.main) { delay(timeMillis = 1_800) @@ -231,6 +237,8 @@ internal class WalletViewModel @Inject constructor( } override fun onScanCardClick() { + analyticsEventsHandler.send(WalletScreenAnalyticsEvent.NoticeScanYourCardTapped) + viewModelScope.launch(dispatchers.io) { scanCardProcessor.scan() .doOnSuccess { @@ -330,7 +338,10 @@ internal class WalletViewModel @Inject constructor( override fun onDetailsClick() = router.openDetailsScreen() - override fun onBackupCardClick() = router.openOnboardingScreen() + override fun onBackupCardClick() { + analyticsEventsHandler.send(WalletScreenAnalyticsEvent.NoticeBackupYourWalletTapped) + router.openOnboardingScreen() + } override fun onCriticalWarningAlreadySignedHashesClick() { uiState = stateFactory.getStateWithOpenWalletBottomSheet( @@ -363,6 +374,8 @@ internal class WalletViewModel @Inject constructor( } override fun onWalletChange(index: Int) { + analyticsEventsHandler.send(WalletScreenAnalyticsEvent.WalletSwipe) + val state = uiState as? WalletState.ContentState ?: return if (state.walletsListConfig.selectedWalletIndex == index) return @@ -417,8 +430,14 @@ internal class WalletViewModel @Inject constructor( ?: return when (uiState) { - is WalletMultiCurrencyState.Content -> refreshMultiCurrencyContent(selectedWalletIndex) - is WalletSingleCurrencyState.Content -> refreshSingleCurrencyContent(selectedWalletIndex) + is WalletMultiCurrencyState.Content -> { + analyticsEventsHandler.send(PortfolioEvent.Refreshed) + refreshMultiCurrencyContent(selectedWalletIndex) + } + is WalletSingleCurrencyState.Content -> { + analyticsEventsHandler.send(PortfolioEvent.Refreshed) + refreshSingleCurrencyContent(selectedWalletIndex) + } is WalletState.Initial, is WalletMultiCurrencyState.Locked, is WalletSingleCurrencyState.Locked, @@ -427,6 +446,8 @@ internal class WalletViewModel @Inject constructor( } override fun onOrganizeTokensClick() { + analyticsEventsHandler.send(PortfolioEvent.OrganizeTokens) + val state = requireNotNull(uiState as? WalletState.ContentState) val index = state.walletsListConfig.selectedWalletIndex val walletId = state.walletsListConfig.wallets[index].id @@ -533,6 +554,7 @@ internal class WalletViewModel @Inject constructor( } override fun onManageTokensClick() { + analyticsEventsHandler.send(PortfolioEvent.ButtonManageTokens) reduxStateHolder.dispatch(action = TokensAction.SetArgs.ManageAccess) router.openManageTokensScreen() } @@ -567,6 +589,8 @@ internal class WalletViewModel @Inject constructor( } override fun onUnlockWalletClick() { + analyticsEventsHandler.send(WalletScreenAnalyticsEvent.NoticeWalletLocked) + viewModelScope.launch(dispatchers.io) { unlockWalletsUseCase() } @@ -586,6 +610,7 @@ internal class WalletViewModel @Inject constructor( } override fun onTokenItemClick(currency: CryptoCurrency) { + analyticsEventsHandler.send(PortfolioEvent.TokenTapped) router.openTokenDetails(currency = currency) }