Updated on 2026-08-14
This commit is contained in:
parent
943baed9b8
commit
5e12c2dc29
14 changed files with 238 additions and 11 deletions
|
|
@ -99,6 +99,7 @@ sealed class AnalyticsParam {
|
||||||
data object NewsPage : ScreensSources("News Page")
|
data object NewsPage : ScreensSources("News Page")
|
||||||
data object Portfolio : ScreensSources("Portfolio")
|
data object Portfolio : ScreensSources("Portfolio")
|
||||||
data object Staking : ScreensSources("Staking")
|
data object Staking : ScreensSources("Staking")
|
||||||
|
data object Earn : ScreensSources("Earn")
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class TxSentFrom(val value: String) {
|
sealed class TxSentFrom(val value: String) {
|
||||||
|
|
|
||||||
|
|
@ -132,8 +132,8 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
|
||||||
innerRouter.push(FeedEntryChildFactory.Child.Earn)
|
innerRouter.push(FeedEntryChildFactory.Child.Earn)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openSearch() {
|
override fun openSearch(source: String) {
|
||||||
innerRouter.push(FeedEntryChildFactory.Child.Search)
|
innerRouter.push(FeedEntryChildFactory.Child.Search(source))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ internal class FeedEntryChildFactory @Inject constructor(
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@Immutable
|
@Immutable
|
||||||
data object Search : Child
|
data class Search(val source: String) : Child
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod")
|
||||||
|
|
@ -144,7 +144,7 @@ internal class FeedEntryChildFactory @Inject constructor(
|
||||||
addToPortfolioComponentFactory = addToPortfolioPreselectedDataComponent,
|
addToPortfolioComponentFactory = addToPortfolioPreselectedDataComponent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Child.Search -> DefaultSearchComponent(
|
is Child.Search -> DefaultSearchComponent(
|
||||||
appComponentContext = appComponentContext,
|
appComponentContext = appComponentContext,
|
||||||
params = DefaultSearchComponent.Params(
|
params = DefaultSearchComponent.Params(
|
||||||
onBackClick = onBackClicked,
|
onBackClick = onBackClicked,
|
||||||
|
|
@ -155,6 +155,7 @@ internal class FeedEntryChildFactory @Inject constructor(
|
||||||
source = AnalyticsParam.ScreensSources.Market.value,
|
source = AnalyticsParam.ScreensSources.Market.value,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
sourceParams = child.source,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,6 @@ internal class DefaultEarnComponent(
|
||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
val onBackClick: () -> Unit,
|
val onBackClick: () -> Unit,
|
||||||
val onSearchClicked: () -> Unit,
|
val onSearchClicked: (source: String) -> Unit,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +134,7 @@ internal class DefaultMarketsTokenListComponent(
|
||||||
|
|
||||||
data class ClickIntents(
|
data class ClickIntents(
|
||||||
val onBackClicked: () -> Unit,
|
val onBackClicked: () -> Unit,
|
||||||
val onSearchClicked: () -> Unit,
|
val onSearchClicked: (source: String) -> Unit,
|
||||||
val onTokenClick: ((TokenMarketParams, AppCurrency) -> Unit),
|
val onTokenClick: ((TokenMarketParams, AppCurrency) -> Unit),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,5 +120,6 @@ internal class DefaultSearchComponent(
|
||||||
data class Params(
|
data class Params(
|
||||||
val onBackClick: () -> Unit,
|
val onBackClick: () -> Unit,
|
||||||
val onMarketTokenClick: ((TokenMarketParams, AppCurrency) -> Unit),
|
val onMarketTokenClick: ((TokenMarketParams, AppCurrency) -> Unit),
|
||||||
|
val sourceParams: String,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import com.arkivanov.decompose.router.slot.dismiss
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.common.routing.AppRouter
|
import com.tangem.common.routing.AppRouter
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsParam
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.core.decompose.model.ParamsContainer
|
import com.tangem.core.decompose.model.ParamsContainer
|
||||||
|
|
@ -325,7 +326,7 @@ internal class EarnModel @Inject constructor(
|
||||||
onNetworkFilterClick = ::onNetworkFilterClick,
|
onNetworkFilterClick = ::onNetworkFilterClick,
|
||||||
onTypeFilterClick = ::onTypeFilterClick,
|
onTypeFilterClick = ::onTypeFilterClick,
|
||||||
onScroll = ::onMostlyUsedScrolled,
|
onScroll = ::onMostlyUsedScrolled,
|
||||||
onSearchBarClicked = params.onSearchClicked,
|
onSearchBarClicked = { params.onSearchClicked(AnalyticsParam.ScreensSources.Earn.value) },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,7 @@ internal class FeedComponentModel @Inject constructor(
|
||||||
onBarClick = {
|
onBarClick = {
|
||||||
analyticsEventHandler.send(FeedAnalyticsEvent.TokenSearchedClicked())
|
analyticsEventHandler.send(FeedAnalyticsEvent.TokenSearchedClicked())
|
||||||
if (designFeatureToggles.isRedesignEnabled) {
|
if (designFeatureToggles.isRedesignEnabled) {
|
||||||
params.feedClickIntents.openSearch()
|
params.feedClickIntents.openSearch(AnalyticsParam.ScreensSources.Markets.value)
|
||||||
} else {
|
} else {
|
||||||
params.feedClickIntents.onMarketOpenClick(null)
|
params.feedClickIntents.onMarketOpenClick(null)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,5 +30,5 @@ internal interface FeedModelClickIntents {
|
||||||
|
|
||||||
fun onOpenEarnPage()
|
fun onOpenEarnPage()
|
||||||
|
|
||||||
fun openSearch()
|
fun openSearch(source: String)
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsParam
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.core.decompose.model.ParamsContainer
|
import com.tangem.core.decompose.model.ParamsContainer
|
||||||
|
|
@ -70,7 +71,7 @@ internal class MarketsListModel @Inject constructor(
|
||||||
preselectedInterval = Provider { modelParams.params.preselectedInterval },
|
preselectedInterval = Provider { modelParams.params.preselectedInterval },
|
||||||
onBackClick = modelParams.clickIntents.onBackClicked,
|
onBackClick = modelParams.clickIntents.onBackClicked,
|
||||||
analyticsEventHandler = analyticsEventHandler,
|
analyticsEventHandler = analyticsEventHandler,
|
||||||
onSearchBarClick = modelParams.clickIntents.onSearchClicked,
|
onSearchBarClick = { modelParams.clickIntents.onSearchClicked(AnalyticsParam.ScreensSources.Market.value) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,21 @@ internal class MarketsListBatchFlowManager(
|
||||||
initialValue = false,
|
initialValue = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val initialLoadingError: Flow<Throwable> = batchFlow.state
|
||||||
|
.map { it.status }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.filterIsInstance<PaginationStatus.InitialLoadingError>()
|
||||||
|
.map { it.throwable }
|
||||||
|
|
||||||
|
val totalCount: StateFlow<Int?> = batchFlow.state
|
||||||
|
.map { it.totalCount }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.stateIn(
|
||||||
|
scope = modelScope,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
initialValue = null,
|
||||||
|
)
|
||||||
|
|
||||||
val isSearchNotFoundState = batchFlow.state
|
val isSearchNotFoundState = batchFlow.state
|
||||||
.map { batchListState ->
|
.map { batchListState ->
|
||||||
currentSearchText().isNullOrEmpty().not() &&
|
currentSearchText().isNullOrEmpty().not() &&
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.core.decompose.model.ParamsContainer
|
import com.tangem.core.decompose.model.ParamsContainer
|
||||||
|
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||||
|
|
@ -28,6 +29,7 @@ import com.tangem.features.feed.components.search.SearchBottomSheetRoute
|
||||||
import com.tangem.features.feed.model.market.list.state.MarketsListUM
|
import com.tangem.features.feed.model.market.list.state.MarketsListUM
|
||||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||||
import com.tangem.features.feed.model.market.list.statemanager.MarketsListBatchFlowManager
|
import com.tangem.features.feed.model.market.list.statemanager.MarketsListBatchFlowManager
|
||||||
|
import com.tangem.features.feed.model.search.analytics.SearchAnalyticsHelper
|
||||||
import com.tangem.features.feed.model.search.converter.*
|
import com.tangem.features.feed.model.search.converter.*
|
||||||
import com.tangem.features.feed.model.search.state.SearchStateController
|
import com.tangem.features.feed.model.search.state.SearchStateController
|
||||||
import com.tangem.features.feed.model.search.state.transformers.*
|
import com.tangem.features.feed.model.search.state.transformers.*
|
||||||
|
|
@ -47,6 +49,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
private const val UPDATE_QUOTES_TIMER_MILLIS = 60000L
|
private const val UPDATE_QUOTES_TIMER_MILLIS = 60000L
|
||||||
private const val MARKET_SEARCH_DEBOUNCE_MS = 500L
|
private const val MARKET_SEARCH_DEBOUNCE_MS = 500L
|
||||||
|
private const val RESULTS_SHOWN_DEBOUNCE_MS = 1000L
|
||||||
|
|
||||||
@Suppress("LongParameterList", "LargeClass")
|
@Suppress("LongParameterList", "LargeClass")
|
||||||
@ModelScoped
|
@ModelScoped
|
||||||
|
|
@ -63,6 +66,7 @@ internal class SearchModel @Inject constructor(
|
||||||
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
|
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
|
||||||
private val appRouter: AppRouter,
|
private val appRouter: AppRouter,
|
||||||
private val stateController: SearchStateController,
|
private val stateController: SearchStateController,
|
||||||
|
private val searchAnalyticsHelper: SearchAnalyticsHelper,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private val params = paramsContainer.require<DefaultSearchComponent.Params>()
|
private val params = paramsContainer.require<DefaultSearchComponent.Params>()
|
||||||
|
|
@ -71,7 +75,6 @@ internal class SearchModel @Inject constructor(
|
||||||
private val searchResultsJob = JobHolder()
|
private val searchResultsJob = JobHolder()
|
||||||
private val marketSearchDebounceJob = JobHolder()
|
private val marketSearchDebounceJob = JobHolder()
|
||||||
private var shouldShowAllTokensIncludingUnder100k = false
|
private var shouldShowAllTokensIncludingUnder100k = false
|
||||||
|
|
||||||
private val currentAppCurrency = getSelectedAppCurrencyUseCase().map { maybeAppCurrency ->
|
private val currentAppCurrency = getSelectedAppCurrencyUseCase().map { maybeAppCurrency ->
|
||||||
maybeAppCurrency.getOrElse { AppCurrency.Default }
|
maybeAppCurrency.getOrElse { AppCurrency.Default }
|
||||||
}.stateIn(
|
}.stateIn(
|
||||||
|
|
@ -122,7 +125,10 @@ internal class SearchModel @Inject constructor(
|
||||||
subscribeToMarketUiItems()
|
subscribeToMarketUiItems()
|
||||||
subscribeToQuotesPolling()
|
subscribeToQuotesPolling()
|
||||||
subscribeToAppCurrencyChanges()
|
subscribeToAppCurrencyChanges()
|
||||||
|
subscribeToMarketLoadingErrors()
|
||||||
|
subscribeToResultsShown()
|
||||||
loadHistory()
|
loadHistory()
|
||||||
|
searchAnalyticsHelper.sendSearchScreenOpened(params.sourceParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadMore() {
|
fun loadMore() {
|
||||||
|
|
@ -138,11 +144,14 @@ internal class SearchModel @Inject constructor(
|
||||||
fun clearSearchHistory() {
|
fun clearSearchHistory() {
|
||||||
modelScope.launch(dispatchers.default) {
|
modelScope.launch(dispatchers.default) {
|
||||||
clearSearchHistoryUseCase()
|
clearSearchHistoryUseCase()
|
||||||
|
searchAnalyticsHelper.sendClearButtonClicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTextHintClick(text: String) {
|
fun onTextHintClick(text: String) {
|
||||||
stateController.update(UpdateSearchBarQueryTransformer(text))
|
stateController.update(UpdateSearchBarQueryTransformer(text))
|
||||||
|
searchAnalyticsHelper.sendHintClicked(text)
|
||||||
|
searchAnalyticsHelper.sendSearchStarted()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onResultMarketTokenClick(item: MarketsListItemUM) {
|
fun onResultMarketTokenClick(item: MarketsListItemUM) {
|
||||||
|
|
@ -155,6 +164,7 @@ internal class SearchModel @Inject constructor(
|
||||||
)
|
)
|
||||||
saveRecentSearchTokenUseCase(marketsListItemToRecentSearchTokenConverter.convert(input))
|
saveRecentSearchTokenUseCase(marketsListItemToRecentSearchTokenConverter.convert(input))
|
||||||
saveSearchQueryUseCase(stateController.value.searchBar.query)
|
saveSearchQueryUseCase(stateController.value.searchBar.query)
|
||||||
|
searchAnalyticsHelper.sendMarketItemClicked(item.currencySymbol)
|
||||||
withContext(dispatchers.mainImmediate) {
|
withContext(dispatchers.mainImmediate) {
|
||||||
searchMarketsListManager.getTokenById(item.id)?.let { found ->
|
searchMarketsListManager.getTokenById(item.id)?.let { found ->
|
||||||
params.onMarketTokenClick(found.toSerializableParam(), appCurrency)
|
params.onMarketTokenClick(found.toSerializableParam(), appCurrency)
|
||||||
|
|
@ -176,6 +186,7 @@ internal class SearchModel @Inject constructor(
|
||||||
),
|
),
|
||||||
imageUrl = item.iconUrl,
|
imageUrl = item.iconUrl,
|
||||||
)
|
)
|
||||||
|
searchAnalyticsHelper.sendRecentItemClicked(item.currencySymbol)
|
||||||
params.onMarketTokenClick(tokenMarketParams, currentAppCurrency.value)
|
params.onMarketTokenClick(tokenMarketParams, currentAppCurrency.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,6 +206,7 @@ internal class SearchModel @Inject constructor(
|
||||||
|
|
||||||
private fun onQueryChange(query: String) {
|
private fun onQueryChange(query: String) {
|
||||||
stateController.update(UpdateSearchBarQueryTransformer(query))
|
stateController.update(UpdateSearchBarQueryTransformer(query))
|
||||||
|
searchAnalyticsHelper.sendSearchStarted()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onClearClick() {
|
private fun onClearClick() {
|
||||||
|
|
@ -202,6 +214,7 @@ internal class SearchModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSingleUserAssetClick(entry: UserAssetSearchEntry) {
|
private fun onSingleUserAssetClick(entry: UserAssetSearchEntry) {
|
||||||
|
searchAnalyticsHelper.sendPortfolioItemClicked(entry.currencyStatus.currency.symbol)
|
||||||
appRouter.push(
|
appRouter.push(
|
||||||
AppRoute.CurrencyDetails(
|
AppRoute.CurrencyDetails(
|
||||||
userWalletId = entry.userWalletId,
|
userWalletId = entry.userWalletId,
|
||||||
|
|
@ -211,6 +224,7 @@ internal class SearchModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onGroupedUserAssetClick(grouped: UserAssetSearchItem.Grouped) {
|
private fun onGroupedUserAssetClick(grouped: UserAssetSearchItem.Grouped) {
|
||||||
|
searchAnalyticsHelper.sendGroupClicked(grouped.tokenSymbol)
|
||||||
bottomSheetNavigation.activate(
|
bottomSheetNavigation.activate(
|
||||||
SearchBottomSheetRoute.TokenSelector(
|
SearchBottomSheetRoute.TokenSelector(
|
||||||
entries = grouped.entries,
|
entries = grouped.entries,
|
||||||
|
|
@ -291,6 +305,50 @@ internal class SearchModel @Inject constructor(
|
||||||
}.launchIn(modelScope)
|
}.launchIn(modelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun subscribeToMarketLoadingErrors() {
|
||||||
|
searchMarketsListManager.initialLoadingError
|
||||||
|
.onEach { throwable ->
|
||||||
|
val (code, message) = when (throwable) {
|
||||||
|
is ApiResponseError.HttpException ->
|
||||||
|
throwable.code.numericCode to throwable.message.orEmpty()
|
||||||
|
else -> null to throwable.message.orEmpty()
|
||||||
|
}
|
||||||
|
searchAnalyticsHelper.sendErrorMarketsData(code, message)
|
||||||
|
}
|
||||||
|
.launchIn(modelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||||
|
private fun subscribeToResultsShown() {
|
||||||
|
stateController.uiState
|
||||||
|
.map { it.searchBar.query.trim() }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.flatMapLatest { query ->
|
||||||
|
if (query.isEmpty()) {
|
||||||
|
emptyFlow()
|
||||||
|
} else {
|
||||||
|
combine(
|
||||||
|
searchMarketsListManager.totalCount.filterNotNull(),
|
||||||
|
stateController.uiState.map { state ->
|
||||||
|
(state.content as? SearchContentUM.Results)?.userAssets?.size ?: 0
|
||||||
|
}.distinctUntilChanged(),
|
||||||
|
) { marketCount, userAssetsCount ->
|
||||||
|
marketCount to userAssetsCount
|
||||||
|
}
|
||||||
|
.debounce(RESULTS_SHOWN_DEBOUNCE_MS)
|
||||||
|
.take(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onEach { (marketCount, userAssetsCount) ->
|
||||||
|
searchAnalyticsHelper.sendResultShown(
|
||||||
|
totalResultsCount = marketCount + userAssetsCount,
|
||||||
|
marketsResultsCount = marketCount,
|
||||||
|
userTokensResultsCount = userAssetsCount,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.launchIn(modelScope)
|
||||||
|
}
|
||||||
|
|
||||||
private fun subscribeToMarketUiItems() {
|
private fun subscribeToMarketUiItems() {
|
||||||
combine(
|
combine(
|
||||||
flow = stateController.uiState.map { it.searchBar.query }.distinctUntilChanged(),
|
flow = stateController.uiState.map { it.searchBar.query }.distinctUntilChanged(),
|
||||||
|
|
@ -312,6 +370,7 @@ internal class SearchModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
|
.distinctUntilChanged()
|
||||||
.onEach { snapshot ->
|
.onEach { snapshot ->
|
||||||
stateController.update(ApplySearchMarketBatchTransformer(snapshot))
|
stateController.update(ApplySearchMarketBatchTransformer(snapshot))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
package com.tangem.features.feed.model.search.analytics
|
||||||
|
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_MESSAGE
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
||||||
|
import com.tangem.core.analytics.models.IS_NOT_HTTP_ERROR
|
||||||
|
|
||||||
|
internal sealed class SearchAnalyticsEvent(
|
||||||
|
event: String,
|
||||||
|
params: Map<String, String> = emptyMap(),
|
||||||
|
) : AnalyticsEvent(category = "Search", event = event, params = params) {
|
||||||
|
|
||||||
|
data class SearchScreenOpened(
|
||||||
|
private val screensSource: String,
|
||||||
|
) : SearchAnalyticsEvent(
|
||||||
|
event = "Search Screen Opened",
|
||||||
|
params = mapOf(SOURCE to screensSource),
|
||||||
|
)
|
||||||
|
|
||||||
|
class SearchStarted : SearchAnalyticsEvent(event = "Search Started")
|
||||||
|
|
||||||
|
data class ResultsShown(
|
||||||
|
private val totalResultsCount: Int,
|
||||||
|
private val marketsResultsCount: Int,
|
||||||
|
private val userTokensResultsCount: Int,
|
||||||
|
) : SearchAnalyticsEvent(
|
||||||
|
event = "Results Shown",
|
||||||
|
params = mapOf(
|
||||||
|
"Total Results" to totalResultsCount.toString(),
|
||||||
|
"User Tokens Count" to userTokensResultsCount.toString(),
|
||||||
|
"Market Tokens Count" to marketsResultsCount.toString(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ErrorMarketsData(
|
||||||
|
private val code: Int?,
|
||||||
|
private val message: String,
|
||||||
|
) : SearchAnalyticsEvent(
|
||||||
|
event = "Error - Markets Data",
|
||||||
|
params = mapOf(
|
||||||
|
ERROR_CODE to (code ?: IS_NOT_HTTP_ERROR).toString(),
|
||||||
|
ERROR_MESSAGE to message,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PortfolioItemClicked(
|
||||||
|
private val tokenSymbol: String,
|
||||||
|
) : SearchAnalyticsEvent(
|
||||||
|
event = "Portfolio Item Clicked",
|
||||||
|
params = mapOf(TOKEN_PARAM to tokenSymbol),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class HintClicked(
|
||||||
|
private val hint: String,
|
||||||
|
) : SearchAnalyticsEvent(
|
||||||
|
event = "Hint Clicked",
|
||||||
|
params = mapOf("Text" to hint),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class RecentItemClicked(
|
||||||
|
private val tokenSymbol: String,
|
||||||
|
) : SearchAnalyticsEvent(
|
||||||
|
event = "Recent Item Clicked",
|
||||||
|
params = mapOf(TOKEN_PARAM to tokenSymbol),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MarketItemClicked(
|
||||||
|
private val tokenSymbol: String,
|
||||||
|
) : SearchAnalyticsEvent(
|
||||||
|
event = "Market Item Clicked",
|
||||||
|
params = mapOf(TOKEN_PARAM to tokenSymbol),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class GroupClicked(
|
||||||
|
private val tokenSymbol: String,
|
||||||
|
) : SearchAnalyticsEvent(
|
||||||
|
event = "Group Clicked",
|
||||||
|
params = mapOf(TOKEN_PARAM to tokenSymbol),
|
||||||
|
)
|
||||||
|
|
||||||
|
class ButtonClearHistoryClick : SearchAnalyticsEvent(event = "Button - Clear History")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.tangem.features.feed.model.search.analytics
|
||||||
|
|
||||||
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SearchAnalyticsHelper @Inject constructor(
|
||||||
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private var isSearchStartedWasSent = false
|
||||||
|
|
||||||
|
fun sendSearchScreenOpened(source: String) {
|
||||||
|
analyticsEventHandler.send(SearchAnalyticsEvent.SearchScreenOpened(source))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendMarketItemClicked(tokenSymbol: String) {
|
||||||
|
analyticsEventHandler.send(SearchAnalyticsEvent.MarketItemClicked(tokenSymbol))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendClearButtonClicked() {
|
||||||
|
analyticsEventHandler.send(SearchAnalyticsEvent.ButtonClearHistoryClick())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendHintClicked(text: String) {
|
||||||
|
analyticsEventHandler.send(SearchAnalyticsEvent.HintClicked(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendRecentItemClicked(tokenSymbol: String) {
|
||||||
|
analyticsEventHandler.send(SearchAnalyticsEvent.RecentItemClicked(tokenSymbol))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendSearchStarted() {
|
||||||
|
if (isSearchStartedWasSent) return
|
||||||
|
analyticsEventHandler.send(SearchAnalyticsEvent.SearchStarted())
|
||||||
|
isSearchStartedWasSent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendPortfolioItemClicked(tokenSymbol: String) {
|
||||||
|
analyticsEventHandler.send(SearchAnalyticsEvent.PortfolioItemClicked(tokenSymbol))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendGroupClicked(tokenSymbol: String) {
|
||||||
|
analyticsEventHandler.send(SearchAnalyticsEvent.GroupClicked(tokenSymbol))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendResultShown(totalResultsCount: Int, marketsResultsCount: Int, userTokensResultsCount: Int) {
|
||||||
|
analyticsEventHandler.send(
|
||||||
|
SearchAnalyticsEvent.ResultsShown(
|
||||||
|
totalResultsCount = totalResultsCount,
|
||||||
|
marketsResultsCount = marketsResultsCount,
|
||||||
|
userTokensResultsCount = userTokensResultsCount,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendErrorMarketsData(code: Int?, message: String) {
|
||||||
|
analyticsEventHandler.send(
|
||||||
|
SearchAnalyticsEvent.ErrorMarketsData(
|
||||||
|
code = code,
|
||||||
|
message = message,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue