diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 0f2952af12..7d7c0cc50d 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -442,6 +442,7 @@ internal class ChildFactory @Inject constructor( selectedCurrency = route.selectedCurrency, source = ChooseManagedTokensComponent.Source.valueOf(route.source.name), showSendViaSwapNotification = route.showSendViaSwapNotification, + analyticsCategoryName = route.analyticsCategoryName, ), componentFactory = chooseManagedTokensComponentFactory, ) diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index f36983b866..de65bd8448 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -134,6 +134,7 @@ sealed class AppRoute(val path: String) : Route { val selectedCurrency: CryptoCurrency?, val source: Source, val showSendViaSwapNotification: Boolean, + val analyticsCategoryName: String, ) : AppRoute(path = "/$source/choose_managed_tokens/$userWalletId/${initialCurrency.id.value}") { enum class Source { SendViaSwap, 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 b85be1b1aa..9257e90ce8 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 @@ -234,5 +234,6 @@ sealed class AnalyticsParam { const val RECEIVE_TOKEN = "Receive Token" const val SEND_BLOCKCHAIN = "Send Blockchain" const val RECEIVE_BLOCKCHAIN = "Receive Blockchain" + const val CHOSEN_TOKEN = "Token Chosen" } } \ No newline at end of file diff --git a/features/manage-tokens/api/build.gradle.kts b/features/manage-tokens/api/build.gradle.kts index c3a7b45a50..b1eb0e3830 100644 --- a/features/manage-tokens/api/build.gradle.kts +++ b/features/manage-tokens/api/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { /* Project - Core */ implementation(projects.core.ui) implementation(projects.core.decompose) + implementation(projects.core.analytics.models) /* Compose */ implementation(deps.compose.runtime) diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ChooseManagedTokensComponent.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ChooseManagedTokensComponent.kt index c5d28df18c..b3616e31a9 100644 --- a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ChooseManagedTokensComponent.kt +++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ChooseManagedTokensComponent.kt @@ -14,6 +14,7 @@ interface ChooseManagedTokensComponent : ComposableContentComponent { val source: Source, val showSendViaSwapNotification: Boolean, val callback: ModelCallback? = null, + val analyticsCategoryName: String, ) enum class Source { diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/analytics/CommonManageTokensAnalyticEvents.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/analytics/CommonManageTokensAnalyticEvents.kt new file mode 100644 index 0000000000..eba0758c21 --- /dev/null +++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/analytics/CommonManageTokensAnalyticEvents.kt @@ -0,0 +1,33 @@ +package com.tangem.features.managetokens.component.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN +import com.tangem.core.analytics.models.AnalyticsParam.Key.CHOSEN_TOKEN +import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM + +sealed class CommonManageTokensAnalyticEvents( + category: String, + event: String, + params: Map = mapOf(), +) : AnalyticsEvent(category = category, event = event, params = params) { + + data class TokenSearchClicked( + val categoryName: String, + ) : CommonManageTokensAnalyticEvents(category = categoryName, event = "Token Search Clicked") + + /** Searched token chosen event */ + data class TokenSearched( + val categoryName: String, + val token: String?, + val blockchain: String?, + val isTokenChosen: Boolean, + ) : CommonManageTokensAnalyticEvents( + category = categoryName, + event = "Token Searched", + params = buildMap { + put(CHOSEN_TOKEN, if (isTokenChosen) "Yes" else "No") + token?.let { put(TOKEN_PARAM, token) } + blockchain?.let { put(BLOCKCHAIN, blockchain) } + }, + ) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/DefaultChooseManagedTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/DefaultChooseManagedTokensComponent.kt index 99206f1591..1ff16dac71 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/DefaultChooseManagedTokensComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/DefaultChooseManagedTokensComponent.kt @@ -59,9 +59,11 @@ internal class DefaultChooseManagedTokensComponent @AssistedInject constructor( context = childByContext(componentContext), params = SwapChooseTokenNetworkComponent.Params( userWalletId = config.userWalletId, + analyticsCategoryName = params.analyticsCategoryName, initialCurrency = config.initialCurrency, selectedCurrency = config.selectedCurrency, token = config.token, + isSearchedToken = config.isSearchedToken, onDismiss = model.bottomSheetNavigation::dismiss, onResult = { swapCurrencies, cryptoCurrency -> componentScope.launch { diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/entity/ChooseManageTokensBottomSheetConfig.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/entity/ChooseManageTokensBottomSheetConfig.kt index 77ee44682a..35d7732ad6 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/entity/ChooseManageTokensBottomSheetConfig.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/entity/ChooseManageTokensBottomSheetConfig.kt @@ -14,5 +14,6 @@ internal sealed class ChooseManageTokensBottomSheetConfig { val initialCurrency: CryptoCurrency, val selectedCurrency: CryptoCurrency?, val token: ManagedCryptoCurrency.Token, + val isSearchedToken: Boolean, ) : ChooseManageTokensBottomSheetConfig() } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/model/ChooseManagedTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/model/ChooseManagedTokensModel.kt index 53b4245820..3c239162b1 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/model/ChooseManagedTokensModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/model/ChooseManagedTokensModel.kt @@ -5,6 +5,7 @@ import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.tangem.common.ui.notifications.NotificationId import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -22,6 +23,7 @@ import com.tangem.features.managetokens.choosetoken.entity.ChooseManagedTokenUM import com.tangem.features.managetokens.component.ChooseManagedTokensComponent import com.tangem.features.managetokens.component.ChooseManagedTokensComponent.Source import com.tangem.features.managetokens.component.ManageTokensSource +import com.tangem.features.managetokens.component.analytics.CommonManageTokensAnalyticEvents import com.tangem.features.managetokens.entity.item.CurrencyItemUM import com.tangem.features.managetokens.entity.managetokens.ManageTokensTopBarUM import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM @@ -40,12 +42,14 @@ import timber.log.Timber import javax.inject.Inject import kotlin.collections.isNotEmpty +@Suppress("LongParameterList") @ModelScoped internal class ChooseManagedTokensModel @Inject constructor( private val router: Router, override val dispatchers: CoroutineDispatcherProvider, private val uiMessageSender: UiMessageSender, private val setShouldShowNotificationUseCase: SetShouldShowNotificationUseCase, + private val analyticsEventHandler: AnalyticsEventHandler, paramsContainer: ParamsContainer, manageTokensListManagerFactory: ManageTokensListManager.Factory, ) : Model() { @@ -60,6 +64,7 @@ internal class ChooseManagedTokensModel @Inject constructor( initialCurrency = params.initialCurrency, selectedCurrency = params.selectedCurrency, token = token, + isSearchedToken = uiState.value.readContent.search.isActive, ), ) }, @@ -132,6 +137,17 @@ internal class ChooseManagedTokensModel @Inject constructor( private fun observeSearchQueryChanges() { uiState .distinctUntilChanged { old, new -> + if (!new.readContent.search.isActive && old.readContent.search.isActive) { + analyticsEventHandler.send( + CommonManageTokensAnalyticEvents.TokenSearched( + params.analyticsCategoryName, + token = null, + blockchain = null, + isTokenChosen = false, + ), + ) + } + // It's also used to skip search activation to avoid searching an empty query old.readContent.search.query == new.readContent.search.query && new.readContent.search.isActive } @@ -251,6 +267,11 @@ internal class ChooseManagedTokensModel @Inject constructor( } private fun toggleSearchBar(isActive: Boolean) { + if (isActive) { + analyticsEventHandler.send( + CommonManageTokensAnalyticEvents.TokenSearchClicked(params.analyticsCategoryName), + ) + } uiState.update { state -> @StringRes val placeholderTextRes = if (isActive) { R.string.manage_tokens_search_placeholder diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/DefaultSendEntryPointComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/DefaultSendEntryPointComponent.kt index 0985a678fb..67396045c9 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/DefaultSendEntryPointComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/DefaultSendEntryPointComponent.kt @@ -24,6 +24,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.features.managetokens.component.ChooseManagedTokensComponent import com.tangem.features.send.v2.api.SendComponent import com.tangem.features.send.v2.api.SendEntryPointComponent +import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents import com.tangem.features.send.v2.entrypoint.model.SendEntryPointModel import com.tangem.features.swap.v2.api.SendWithSwapComponent import dagger.assisted.Assisted @@ -126,6 +127,7 @@ internal class DefaultSendEntryPointComponent @AssistedInject constructor( selectedCurrency = null, showSendViaSwapNotification = showSendViaSwapNotification, callback = model, + analyticsCategoryName = CommonSendAnalyticEvents.SEND_CATEGORY, ), ) } diff --git a/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/choosetoken/SwapChooseTokenNetworkComponent.kt b/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/choosetoken/SwapChooseTokenNetworkComponent.kt index 15418e847b..8fa0dc468d 100644 --- a/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/choosetoken/SwapChooseTokenNetworkComponent.kt +++ b/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/choosetoken/SwapChooseTokenNetworkComponent.kt @@ -12,8 +12,10 @@ interface SwapChooseTokenNetworkComponent : ComposableBottomSheetComponent { data class Params( val userWalletId: UserWalletId, val initialCurrency: CryptoCurrency, + val analyticsCategoryName: String, val selectedCurrency: CryptoCurrency?, val token: ManagedCryptoCurrency.Token, + val isSearchedToken: Boolean, val onDismiss: () -> Unit, val onResult: (SwapCurrencies, CryptoCurrency) -> Unit, ) diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt index 8ff3dc702c..5973ba9c7d 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt @@ -270,6 +270,7 @@ internal class SwapAmountModel @Inject constructor( selectedCurrency = selectedCurrency.takeIf { isEditMode }, source = AppRoute.ChooseManagedTokens.Source.SendViaSwap, showSendViaSwapNotification = showSendViaSwapNotification, + analyticsCategoryName = params.analyticsCategoryName, ), ) } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/choosetoken/fromSupported/model/SwapChooseTokenNetworkModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/choosetoken/fromSupported/model/SwapChooseTokenNetworkModel.kt index 496e806384..a765bcee71 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/choosetoken/fromSupported/model/SwapChooseTokenNetworkModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/choosetoken/fromSupported/model/SwapChooseTokenNetworkModel.kt @@ -1,6 +1,7 @@ package com.tangem.features.swap.v2.impl.choosetoken.fromSupported.model import arrow.core.getOrElse +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -10,6 +11,8 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.swap.models.SwapCurrencies import com.tangem.domain.swap.usecase.GetSwapSupportedPairsUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.features.managetokens.component.analytics.CommonManageTokensAnalyticEvents +import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkComponent import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkContentUM import com.tangem.features.swap.v2.impl.choosetoken.fromSupported.entity.SwapChooseTokenNetworkUM @@ -26,6 +29,7 @@ import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject +@Suppress("LongParameterList") @ModelScoped internal class SwapChooseTokenNetworkModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, @@ -34,6 +38,7 @@ internal class SwapChooseTokenNetworkModel @Inject constructor( private val createCryptoCurrencyUseCase: CreateCryptoCurrencyUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val swapChooseTokenAlertFactory: SwapChooseTokenAlertFactory, + private val analyticsEventHandler: AnalyticsEventHandler, ) : Model() { private val params: SwapChooseTokenNetworkComponent.Params = paramsContainer.require() @@ -103,6 +108,23 @@ internal class SwapChooseTokenNetworkModel @Inject constructor( private fun onSwapTokenClick(swapCurrencies: SwapCurrencies, cryptoCurrency: CryptoCurrency) { val prevSelectedCurrency = params.selectedCurrency?.network + analyticsEventHandler.send( + CommonSendAnalyticEvents.TokenChosen( + categoryName = params.analyticsCategoryName, + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + ), + ) + if (params.isSearchedToken) { + analyticsEventHandler.send( + CommonManageTokensAnalyticEvents.TokenSearched( + categoryName = params.analyticsCategoryName, + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + isTokenChosen = true, + ), + ) + } if (prevSelectedCurrency == null || cryptoCurrency.network == prevSelectedCurrency) { params.onResult(swapCurrencies, cryptoCurrency) } else {