Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-28 12:06:52 +04:00
parent 34dd15bea5
commit 88400cd0d5
11 changed files with 208 additions and 41 deletions

View file

@ -1,25 +0,0 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.models.AnalyticsEvent
/**
[REDACTED_AUTHOR]
*/
sealed class MainScreen(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("Main Screen", event, params) {
class ScreenOpened : MainScreen("Screen opened")
class ButtonScanCard : MainScreen("Button - Scan Card")
class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen(
event = "Enable Biometric",
params = mapOf("State" to state.value),
)
class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen(
event = "Notice - Rate The App Button Tapped",
params = mapOf("Result" to result.value),
)
}

View file

@ -9,7 +9,6 @@ import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.proxy.redux.DaggerGraphState
@ -31,7 +30,7 @@ class CardContextInterceptor(
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean {
return when (event) {
is IntroductionProcess.ButtonScanCard, is MainScreen.ButtonScanCard -> false
is IntroductionProcess.ButtonScanCard -> false
else -> true
}
}

View file

@ -5,11 +5,11 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.dispatchWithMain
@ -110,7 +110,11 @@ internal class SaveWalletMiddleware {
// TODO: Remove after onboarding refactoring
Analytics.send(Onboarding.EnableBiometrics(AnalyticsParam.OnOffState.On))
} else {
Analytics.send(MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.On))
Analytics.send(
MainScreenAnalyticsEvent.EnableBiometrics(
state = com.tangem.core.analytics.models.AnalyticsParam.OnOffState.On,
),
)
}
/*
@ -148,7 +152,11 @@ internal class SaveWalletMiddleware {
// TODO: Remove after onboarding refactoring
Analytics.send(Onboarding.EnableBiometrics(AnalyticsParam.OnOffState.Off))
} else {
Analytics.send(MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.Off))
Analytics.send(
MainScreenAnalyticsEvent.EnableBiometrics(
com.tangem.core.analytics.models.AnalyticsParam.OnOffState.Off,
),
)
}
}

View file

@ -76,6 +76,9 @@ sealed class AnalyticsParam {
data object MyWallets : ScreensSources("My Wallets")
data object Token : ScreensSources("Token")
data object Stories : ScreensSources("Stories")
data object Buy : ScreensSources("Buy")
data object Swap : ScreensSources("Swap")
data object Sell : ScreensSources("Sell")
}
sealed class TxSentFrom(val value: String) {
@ -161,6 +164,12 @@ sealed class AnalyticsParam {
}
}
enum class Status(val value: String) {
Success(value = "Success"),
Error(value = "Error"),
Pending(value = "Pending"),
}
companion object Key {
const val BLOCKCHAIN = "blockchain"
const val TOKEN_PARAM = "Token"

View file

@ -0,0 +1,75 @@
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.AnalyticsParam.Key.TOKEN_PARAM
/**
* Main screen analytics event
*
* @param event event name
* @param params params
*/
sealed class MainScreenAnalyticsEvent(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(category = "Main Screen", event = event, params = params) {
class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreenAnalyticsEvent(
event = "Enable Biometric",
params = mapOf("State" to state.value),
)
// region Action Buttons feature
data class ButtonBuy(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
event = "Button - Buy",
params = mapOf(AnalyticsParam.STATUS to status.value),
)
data class ButtonSwap(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
event = "Button - Swap",
params = mapOf(AnalyticsParam.STATUS to status.value),
)
data class ButtonSell(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
event = "Button - Sell",
params = mapOf(AnalyticsParam.STATUS to status.value),
)
data object BuyScreenOpened : MainScreenAnalyticsEvent(event = "Buy Screen Opened")
data object SwapScreenOpened : MainScreenAnalyticsEvent(event = "Swap Screen Opened")
data object SellScreenOpened : MainScreenAnalyticsEvent(event = "Sell Screen Opened")
data class BuyTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
event = "Buy Token Clicked",
params = mapOf(TOKEN_PARAM to currencySymbol),
)
data class SellTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
event = "Sell Token Clicked",
params = mapOf(TOKEN_PARAM to currencySymbol),
)
data class SwapTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
event = "Swap Token Clicked",
params = mapOf(TOKEN_PARAM to currencySymbol),
)
data class ReceiveTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
event = "Receive Token Clicked",
params = mapOf(TOKEN_PARAM to currencySymbol),
)
data class RemoveTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
event = "Remove Button Clicked",
params = mapOf(TOKEN_PARAM to currencySymbol),
)
data class ButtonClose(val source: AnalyticsParam.ScreensSources) : MainScreenAnalyticsEvent(
event = "Button - Close",
params = mapOf(AnalyticsParam.SOURCE to source.value),
)
// endregion
}

View file

@ -19,10 +19,11 @@ dependencies {
implementation(projects.features.swap.domain.models)
/** Project - Core */
implementation(projects.core.decompose)
implementation(projects.core.ui)
implementation(projects.core.analytics)
implementation(projects.core.configToggles)
implementation(projects.core.decompose)
implementation(projects.core.navigation)
implementation(projects.core.ui)
/** Project - Common */
implementation(projects.common.routing)

View file

@ -3,6 +3,9 @@ package com.tangem.features.onramp.selecttoken
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import arrow.core.getOrElse
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
@ -20,6 +23,8 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.launch
// TODO: Add OnrampOperationModel
@Suppress("LongParameterList")
internal class DefaultOnrampOperationComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
onrampTokenListComponentFactory: OnrampTokenListComponent.Factory,
@ -27,6 +32,7 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor(
private val reduxStateHolder: ReduxStateHolder,
private val getWalletsUseCase: GetWalletsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
) : AppComponentContext by appComponentContext, OnrampOperationComponent {
private val onrampTokenListComponent: OnrampTokenListComponent = onrampTokenListComponentFactory.create(
@ -42,6 +48,15 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor(
),
)
init {
analyticsEventHandler.send(
event = when (params) {
is OnrampOperationComponent.Params.Buy -> MainScreenAnalyticsEvent.BuyScreenOpened
is OnrampOperationComponent.Params.Sell -> MainScreenAnalyticsEvent.SellScreenOpened
},
)
}
@Composable
override fun Content(modifier: Modifier) {
OnrampSelectToken(
@ -49,13 +64,25 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor(
is OnrampOperationComponent.Params.Buy -> R.string.common_buy
is OnrampOperationComponent.Params.Sell -> R.string.common_sell
},
onBackClick = router::pop,
onBackClick = ::onBackClick,
onrampTokenListComponent = onrampTokenListComponent,
modifier = modifier,
)
}
private fun onTokenClick(status: CryptoCurrencyStatus) {
val currencySymbol = status.currency.symbol
analyticsEventHandler.send(
event = when (params) {
is OnrampOperationComponent.Params.Buy -> {
MainScreenAnalyticsEvent.BuyTokenClicked(currencySymbol = currencySymbol)
}
is OnrampOperationComponent.Params.Sell -> {
MainScreenAnalyticsEvent.SellTokenClicked(currencySymbol = currencySymbol)
}
},
)
componentScope.launch {
val appCurrencyCode = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }.code
@ -76,6 +103,19 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor(
)
}
private fun onBackClick() {
analyticsEventHandler.send(
event = MainScreenAnalyticsEvent.ButtonClose(
source = when (params) {
is OnrampOperationComponent.Params.Buy -> AnalyticsParam.ScreensSources.Buy
is OnrampOperationComponent.Params.Sell -> AnalyticsParam.ScreensSources.Sell
},
),
)
router.pop()
}
@AssistedFactory
interface Factory : OnrampOperationComponent.Factory {

View file

@ -4,6 +4,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.model.getOrCreateModel
@ -21,6 +23,7 @@ import dagger.assisted.AssistedInject
internal class DefaultSwapSelectTokensComponent @AssistedInject constructor(
tokenListComponentFactory: OnrampTokenListComponent.Factory,
availableSwapPairsComponentFactory: AvailableSwapPairsComponent.Factory,
analyticsEventHandler: AnalyticsEventHandler,
@Assisted private val appComponentContext: AppComponentContext,
@Assisted private val params: SwapSelectTokensComponent.Params,
) : AppComponentContext by appComponentContext, SwapSelectTokensComponent {
@ -46,6 +49,10 @@ internal class DefaultSwapSelectTokensComponent @AssistedInject constructor(
),
)
init {
analyticsEventHandler.send(event = MainScreenAnalyticsEvent.SwapScreenOpened)
}
@Composable
override fun Content(modifier: Modifier) {
val state = model.state.collectAsStateWithLifecycle()

View file

@ -1,6 +1,5 @@
package com.tangem.features.onramp.swap.entity
import com.tangem.core.decompose.navigation.Router
import com.tangem.features.onramp.swap.entity.utils.createEmptyExchangeFrom
import com.tangem.features.onramp.swap.entity.utils.createEmptyExchangeTo
import kotlinx.coroutines.flow.MutableStateFlow
@ -12,19 +11,15 @@ import javax.inject.Inject
/**
* [SwapSelectTokensUM] controller
*
* @property router router
*
[REDACTED_AUTHOR]
*/
internal class SwapSelectTokensController @Inject constructor(
private val router: Router,
) {
internal class SwapSelectTokensController @Inject constructor() {
val state: StateFlow<SwapSelectTokensUM> get() = _state
private val _state: MutableStateFlow<SwapSelectTokensUM> = MutableStateFlow(
value = SwapSelectTokensUM(
onBackClick = router::pop,
onBackClick = {},
exchangeFrom = createEmptyExchangeFrom(),
exchangeTo = createEmptyExchangeTo(),
),

View file

@ -1,6 +1,9 @@
package com.tangem.features.onramp.swap.model
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
@ -27,6 +30,7 @@ internal class SwapSelectTokensModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val controller: SwapSelectTokensController,
private val router: Router,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
val state: StateFlow<SwapSelectTokensUM> = controller.state
@ -38,6 +42,12 @@ internal class SwapSelectTokensModel @Inject constructor(
private val params = paramsContainer.require<SwapSelectTokensComponent.Params>()
init {
controller.update {
it.copy(onBackClick = ::onBackClick)
}
}
/**
* Select "from" token
*
@ -45,12 +55,16 @@ internal class SwapSelectTokensModel @Inject constructor(
* @param status crypto currency status
*/
fun selectFromToken(selectedTokenItemState: TokenItemState, status: CryptoCurrencyStatus) {
analyticsEventHandler.send(
event = MainScreenAnalyticsEvent.SwapTokenClicked(currencySymbol = status.currency.symbol),
)
_fromCurrencyStatus.value = status
controller.update(
transformer = SelectFromTokenTransformer(
selectedTokenItemState = selectedTokenItemState,
onRemoveClick = ::removeSelectedFromToken,
onRemoveClick = ::onRemoveFromTokenClick,
),
)
}
@ -62,6 +76,10 @@ internal class SwapSelectTokensModel @Inject constructor(
* @param status crypto currency status
*/
fun selectToToken(selectedTokenItemState: TokenItemState, status: CryptoCurrencyStatus) {
analyticsEventHandler.send(
event = MainScreenAnalyticsEvent.ReceiveTokenClicked(currencySymbol = status.currency.symbol),
)
modelScope.launch {
_toCurrencyStatus.value = status
@ -88,6 +106,26 @@ internal class SwapSelectTokensModel @Inject constructor(
}
}
private fun onBackClick() {
analyticsEventHandler.send(
event = MainScreenAnalyticsEvent.ButtonClose(source = AnalyticsParam.ScreensSources.Swap),
)
router.pop()
}
private fun onRemoveFromTokenClick() {
val currencySymbol = requireNotNull(_fromCurrencyStatus.value?.currency?.symbol) {
"Token was not selected"
}
analyticsEventHandler.send(
event = MainScreenAnalyticsEvent.RemoveTokenClicked(currencySymbol = currencySymbol),
)
removeSelectedFromToken()
}
private fun removeSelectedFromToken() {
_fromCurrencyStatus.value = null

View file

@ -5,6 +5,8 @@ import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.tokens.getUnavailabilityReasonText
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig
@ -439,16 +441,34 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
override fun onMultiWalletSellClick(userWalletId: UserWalletId) {
analyticsEventHandler.send(
event = MainScreenAnalyticsEvent.ButtonSell(
status = AnalyticsParam.Status.Success, // TODO [REDACTED_JIRA]
),
)
// TODO [REDACTED_JIRA]
appRouter.push(route = AppRoute.SellCrypto(userWalletId = userWalletId))
}
override fun onMultiWalletSwapClick(userWalletId: UserWalletId) {
analyticsEventHandler.send(
event = MainScreenAnalyticsEvent.ButtonSwap(
status = AnalyticsParam.Status.Success, // TODO [REDACTED_JIRA]
),
)
// TODO [REDACTED_JIRA]
appRouter.push(route = AppRoute.SwapCrypto(userWalletId = userWalletId))
}
override fun onMultiWalletBuyClick(userWalletId: UserWalletId) {
analyticsEventHandler.send(
event = MainScreenAnalyticsEvent.ButtonBuy(
status = AnalyticsParam.Status.Success, // TODO [REDACTED_JIRA]
),
)
// TODO [REDACTED_JIRA]
appRouter.push(route = AppRoute.BuyCrypto(userWalletId = userWalletId))
}