Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-10 00:17:59 -07:00
parent e73c7b7426
commit 0727b381ca
16 changed files with 277 additions and 18 deletions

View file

@ -22,9 +22,35 @@ sealed class MainScreenAnalyticsEvent(
)
// region Action Buttons feature
data class ButtonBuy(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
data class ButtonBuy(
val status: AnalyticsParam.Status,
val screenType: String? = null,
) : MainScreenAnalyticsEvent(
event = "Button - Buy",
params = mapOf(AnalyticsParam.STATUS to status.value),
params = buildMap {
put(AnalyticsParam.STATUS, status.value)
screenType?.let { put(AnalyticsParam.TYPE, it) }
},
)
data object ButtonReceive : MainScreenAnalyticsEvent(
event = "Button - Receive",
)
data object LimitsClicked : MainScreenAnalyticsEvent(
event = "Limits Clicked",
)
data object NoticeBalancesInfo : MainScreenAnalyticsEvent(
event = "Notice - Balances Info",
)
data object NoticeLimitsInfo : MainScreenAnalyticsEvent(
event = "Notice - Limits Info",
)
data object ButtonExplore : MainScreenAnalyticsEvent(
event = "Button - Explore",
)
data class ButtonSwap(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
@ -83,4 +109,9 @@ sealed class MainScreenAnalyticsEvent(
params = mapOf(ERROR_CODE to errorCode),
)
// endregion
companion object {
const val VISA_TYPE = "Visa"
const val WALLET_TYPE = "Wallet"
}
}

View file

@ -4,6 +4,7 @@ import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.replaceAll
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.models.scan.ProductType
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -12,7 +13,6 @@ import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
@ -29,6 +29,7 @@ import com.tangem.features.onboarding.v2.entry.impl.analytics.OnboardingEntryEve
import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingRoute
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
import com.tangem.features.onboarding.v2.twin.api.OnboardingTwinComponent
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.NonCancellable
@ -150,10 +151,16 @@ internal class OnboardingEntryModel @Inject constructor(
if (askBiometryFeatureToggles.isAskForBiometryEnabled) {
modelScope.launch {
if (tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen()) {
doIfVisa {
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.BiometricScreenOpened)
}
stackNavigation.replaceAll(
OnboardingRoute.AskBiometry(modelCallbacks = AskBiometryModelCallbacks(doneMode)),
)
} else {
doIfVisa {
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.SuccessScreenOpened)
}
stackNavigation.replaceAll(
OnboardingRoute.Done(
mode = doneMode,
@ -163,6 +170,9 @@ internal class OnboardingEntryModel @Inject constructor(
}
}
} else {
doIfVisa {
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.SuccessScreenOpened)
}
stackNavigation.replaceAll(
OnboardingRoute.Done(
mode = doneMode,
@ -177,6 +187,9 @@ internal class OnboardingEntryModel @Inject constructor(
) : AskBiometryComponent.ModelCallbacks {
override fun onAllowed() {
analyticsEventHandler.send(OnboardingEntryEvent.Biometric(OnboardingEntryEvent.Biometric.State.On))
doIfVisa {
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.SuccessScreenOpened)
}
stackNavigation.replaceAll(
OnboardingRoute.Done(
mode = doneMode,
@ -187,6 +200,9 @@ internal class OnboardingEntryModel @Inject constructor(
override fun onDenied() {
analyticsEventHandler.send(OnboardingEntryEvent.Biometric(OnboardingEntryEvent.Biometric.State.Off))
doIfVisa {
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.SuccessScreenOpened)
}
stackNavigation.replaceAll(
OnboardingRoute.Done(
mode = doneMode,
@ -232,6 +248,12 @@ internal class OnboardingEntryModel @Inject constructor(
}
}
private inline fun doIfVisa(function: () -> Unit) {
if (params.scanResponse.productType == ProductType.Visa) {
function()
}
}
inner class OnboardingTwinModelCallbacks : OnboardingTwinComponent.ModelCallbacks {
override fun onDone() {
navigateToFinalScreenFlow()

View file

@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.toHexString
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
@ -19,6 +20,9 @@ import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.domain.visa.repository.VisaAuthRepository
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.OnboardingVisaAccessCodeComponent
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.state.OnboardingVisaAccessCodeUM
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.ONBOARDING_SOURCE
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.VisaAnalyticsEvent
import com.tangem.features.onboarding.v2.visa.impl.common.ActivationReadyEvent
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.sdk.api.visa.VisaCardActivationTaskMode
@ -30,6 +34,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@Suppress("LongParameterList")
@Stable
@ModelScoped
internal class OnboardingVisaAccessCodeModel @Inject constructor(
@ -40,6 +45,7 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
private val tangemSdkManager: TangemSdkManager,
private val visaAuthRepository: VisaAuthRepository,
private val uiMessageSender: UiMessageSender,
private val analyticsEventsHandler: AnalyticsEventHandler,
) : Model() {
private val params: OnboardingVisaAccessCodeComponent.Config = paramsContainer.require()
@ -59,6 +65,10 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
val onBack = MutableSharedFlow<Unit>()
val onDone = MutableSharedFlow<ActivationReadyEvent>()
init {
analyticsEventsHandler.send(OnboardingVisaAnalyticsEvent.SettingAccessCodeStarted)
}
fun onBack() {
if (uiState.value.buttonLoading) return
@ -99,11 +109,14 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
private fun onContinue() {
when (uiState.value.step) {
OnboardingVisaAccessCodeUM.Step.Enter -> {
analyticsEventsHandler.send(OnboardingVisaAnalyticsEvent.AccessCodeEntered)
if (checkAccessCodeMinChars().not()) return
_uiState.update { it.copy(step = OnboardingVisaAccessCodeUM.Step.ReEnter) }
analyticsEventsHandler.send(OnboardingVisaAnalyticsEvent.AccessCodeReenterScreen)
}
OnboardingVisaAccessCodeUM.Step.ReEnter -> {
if (checkAccessCodesMatch().not()) return
analyticsEventsHandler.send(OnboardingVisaAnalyticsEvent.OnboardingVisa)
startActivationProcess(accessCode = uiState.value.accessCodeFirst.text)
}
}
@ -160,6 +173,9 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
is CompletionResult.Failure -> {
loading(false)
uiMessageSender.showErrorDialog(result.error.universalError)
analyticsEventsHandler.send(
VisaAnalyticsEvent.Errors(result.error.code.toString(), ONBOARDING_SOURCE),
)
return@launch
}
is CompletionResult.Success -> result.data

View file

@ -3,6 +3,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.approve.model
import androidx.compose.runtime.Stable
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.toHexString
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
@ -15,6 +16,9 @@ import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.features.onboarding.v2.visa.impl.child.approve.OnboardingVisaApproveComponent
import com.tangem.features.onboarding.v2.visa.impl.child.approve.ui.state.OnboardingVisaApproveUM
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.ONBOARDING_SOURCE
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.VisaAnalyticsEvent
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableSharedFlow
@ -32,6 +36,7 @@ internal class OnboardingVisaApproveModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val tangemSdkManager: TangemSdkManager,
private val uiMessageSender: UiMessageSender,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val params = paramsContainer.require<OnboardingVisaApproveComponent.Config>()
@ -47,6 +52,10 @@ internal class OnboardingVisaApproveModel @Inject constructor(
val uiState = _uiState.asStateFlow()
val onDone = MutableSharedFlow<Unit>()
init {
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.WalletPrepare)
}
private fun getInitialState(): OnboardingVisaApproveUM {
return OnboardingVisaApproveUM(
onApproveClick = ::onApproveClick,
@ -56,6 +65,8 @@ internal class OnboardingVisaApproveModel @Inject constructor(
private fun onApproveClick() {
loading(true)
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ButtonApprove)
modelScope.launch {
val dataToSign = runCatching {
visaActivationRepository.getCustomerWalletAcceptanceData(params.preparationDataForApprove.request)
@ -77,6 +88,9 @@ internal class OnboardingVisaApproveModel @Inject constructor(
is CompletionResult.Failure -> {
loading(false)
uiMessageSender.showErrorDialog(result.error.universalError)
analyticsEventHandler.send(
VisaAnalyticsEvent.Errors(result.error.code.toString(), ONBOARDING_SOURCE),
)
return@launch
}
is CompletionResult.Success -> result.data

View file

@ -1,6 +1,7 @@
package com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.model
import androidx.compose.runtime.Stable
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.ui.extensions.resourceReference
@ -8,6 +9,7 @@ import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.OnboardingVisaChooseWalletComponent.Params.Event
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui.state.OnboardingVisaChooseWalletUM
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui.state.SelectableChainRowUM
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableSharedFlow
@ -21,6 +23,7 @@ import javax.inject.Inject
@ModelScoped
internal class OnboardingVisaChooseWalletModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val _uiState = MutableStateFlow(getInitialState())
@ -53,7 +56,18 @@ internal class OnboardingVisaChooseWalletModel @Inject constructor(
private fun onContinueClicked() {
modelScope.launch {
val selectedOption = _uiState.value.selectedOption ?: return@launch
val value = getAnalyticsValue(selectedOption.event)
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ChooseWalletScreen(value))
onEvent.emit(selectedOption.event)
}
}
private fun getAnalyticsValue(event: Event) = when (event) {
Event.TangemWallet -> {
"Tangem"
}
Event.OtherWallet -> {
"Other"
}
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.inprogress.model
import androidx.compose.runtime.Stable
import com.tangem.common.extensions.toHexString
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
@ -20,6 +21,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent.Config
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent.Params
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.delay
@ -41,6 +43,7 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
private val userWalletsListManager: UserWalletsListManager,
private val uiMessageSender: UiMessageSender,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val params = paramsContainer.require<Config>()
@ -53,6 +56,7 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
val onDone = MutableSharedFlow<Params.DoneEvent>()
init {
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ActivationInProgressScreen)
modelScope.launch {
while (true) {
val result = runCatching {

View file

@ -2,6 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.model
import androidx.compose.runtime.Stable
import com.tangem.common.extensions.toHexString
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
@ -13,6 +14,7 @@ import com.tangem.domain.visa.model.VisaCardId
import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.OnboardingVisaOtherWalletComponent
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui.state.OnboardingVisaOtherWalletUM
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
@ -29,6 +31,7 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val urlOpener: UrlOpener,
private val shareManager: ShareManager,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val config = paramsContainer.require<OnboardingVisaOtherWalletComponent.Config>()
@ -44,6 +47,7 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor(
val onDone = MutableSharedFlow<VisaActivationOrderInfo>()
init {
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.GoToWebsiteOpened)
modelScope.launch {
while (true) {
val result = runCatching {
@ -68,10 +72,12 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor(
}
private fun onShareClicked() {
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ButtonShareLink)
shareManager.shareText("https://tangem.com/") // TODO
}
private fun onOpenInBrowserClicked() {
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ButtonBrowser)
urlOpener.openUrl("https://tangem.com/") // TODO
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.pincode.model
import androidx.compose.runtime.Stable
import com.tangem.common.extensions.toHexString
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
@ -17,6 +18,7 @@ import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.OnboardingVisaPinCodeComponent
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state.OnboardingVisaPinCodeUM
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@ -37,6 +39,7 @@ internal class OnboardingVisaPinCodeModel @Inject constructor(
private val authTokenStorage: VisaAuthTokenStorage,
private val otpStorage: VisaAuthTokenStorage,
private val setVisaPinCodeUseCase: SetVisaPinCodeUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val params = paramsContainer.require<OnboardingVisaPinCodeComponent.Config>()
@ -49,6 +52,10 @@ internal class OnboardingVisaPinCodeModel @Inject constructor(
val uiState = _uiState.asStateFlow()
val onDone = MutableSharedFlow<Unit>()
init {
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.PinCodeScreenOpened)
}
private fun getInitialState(): OnboardingVisaPinCodeUM {
return OnboardingVisaPinCodeUM(
submitButtonEnabled = false,
@ -66,6 +73,7 @@ internal class OnboardingVisaPinCodeModel @Inject constructor(
pinCode = pin,
submitButtonEnabled = PinCodeValidation.validate(pin),
error = if (isError) {
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ErrorPinValidation)
resourceReference(R.string.visa_onboarding_pin_validation_error_message)
} else {
null
@ -76,6 +84,7 @@ internal class OnboardingVisaPinCodeModel @Inject constructor(
}
private fun onSubmitClick() {
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.PinEntered)
val pinCode = _uiState.value.pinCode
if (PinCodeValidation.validate(pinCode).not()) return

View file

@ -2,6 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.welcome.model
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.toHexString
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
@ -14,6 +15,9 @@ import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest
import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.OnboardingVisaWelcomeComponent.Config
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.OnboardingVisaWelcomeComponent.DoneEvent
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.ONBOARDING_SOURCE
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.VisaAnalyticsEvent
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui.state.OnboardingVisaWelcomeUM
import com.tangem.features.onboarding.v2.visa.impl.common.ActivationReadyEvent
import com.tangem.sdk.api.TangemSdkManager
@ -33,6 +37,7 @@ internal class OnboardingVisaWelcomeModel @Inject constructor(
private val tangemSdkManager: TangemSdkManager,
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
private val uiMessageSender: UiMessageSender,
private val analyticsEventsHandler: AnalyticsEventHandler,
) : Model() {
private val params = paramsContainer.require<Config>()
@ -42,6 +47,10 @@ internal class OnboardingVisaWelcomeModel @Inject constructor(
val uiState = _uiState.asStateFlow()
val onDone = MutableSharedFlow<DoneEvent>()
init {
analyticsEventsHandler.send(OnboardingVisaAnalyticsEvent.ActivationScreenOpened)
}
private fun getInitialState(): OnboardingVisaWelcomeUM {
return OnboardingVisaWelcomeUM(
mode = if (params is Config.WelcomeBack) {
@ -54,6 +63,7 @@ internal class OnboardingVisaWelcomeModel @Inject constructor(
}
private fun onContinueClick() {
analyticsEventsHandler.send(OnboardingVisaAnalyticsEvent.ButtonActivate)
if (params !is Config.WelcomeBack) {
modelScope.launch { onDone.emit(DoneEvent.WelcomeDone) }
return
@ -86,6 +96,9 @@ internal class OnboardingVisaWelcomeModel @Inject constructor(
is CompletionResult.Failure -> {
loading(false)
uiMessageSender.showErrorDialog(result.error.universalError)
analyticsEventsHandler.send(
VisaAnalyticsEvent.Errors(result.error.code.toString(), ONBOARDING_SOURCE),
)
return@launch
}
is CompletionResult.Success -> result.data

View file

@ -0,0 +1,85 @@
package com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
internal sealed class OnboardingVisaAnalyticsEvent(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("Onboarding / Visa", event, params) {
data object ActivationScreenOpened : OnboardingVisaAnalyticsEvent(
event = "Activation Screen Opened",
)
data object ButtonActivate : OnboardingVisaAnalyticsEvent(
event = "Button - Activate",
)
data object SettingAccessCodeStarted : OnboardingVisaAnalyticsEvent(
event = "Setting Access Code Started",
)
data object AccessCodeEntered : OnboardingVisaAnalyticsEvent(
event = "Access Code Entered",
)
data object AccessCodeReenterScreen : OnboardingVisaAnalyticsEvent(
event = "Access Code Re-enter Screen",
)
data object OnboardingVisa : OnboardingVisaAnalyticsEvent(
event = "Onboarding / Visa",
)
data class ChooseWalletScreen(
val type: String,
) : OnboardingVisaAnalyticsEvent(
event = "Choose Wallet Screen",
params = mapOf("Type" to type),
)
data object WalletPrepare : OnboardingVisaAnalyticsEvent(
event = "Wallet Prepare",
)
data object ButtonApprove : OnboardingVisaAnalyticsEvent(
event = "Button - Approve",
)
data object GoToWebsiteOpened : OnboardingVisaAnalyticsEvent(
event = "Go To Website Opened",
)
data object ButtonBrowser : OnboardingVisaAnalyticsEvent(
event = "Button - Browser",
)
data object ButtonShareLink : OnboardingVisaAnalyticsEvent(
event = "Button - Share Link",
)
data object ActivationInProgressScreen : OnboardingVisaAnalyticsEvent(
event = "Activation In Progress Screen",
)
data object PinCodeScreenOpened : OnboardingVisaAnalyticsEvent(
event = "PIN Code Screen Opened",
)
data object PinEntered : OnboardingVisaAnalyticsEvent(
event = "PIN Entered",
params = mapOf("Type" to "Visa"),
)
data object BiometricScreenOpened : OnboardingVisaAnalyticsEvent(
event = "Biometric Screen Opened",
)
data object SuccessScreenOpened : OnboardingVisaAnalyticsEvent(
event = "Success Screen Opened",
)
data object ErrorPinValidation : OnboardingVisaAnalyticsEvent(
event = "Error - Pin Validation",
)
}

View file

@ -0,0 +1,23 @@
package com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
internal const val ONBOARDING_SOURCE = "Onboarding"
internal const val MAIN_SOURCE = "Main"
internal sealed class VisaAnalyticsEvent(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("Visa", event, params) {
data class Errors(
val errorCode: String,
val source: String,
) : VisaAnalyticsEvent(
event = "Errors",
params = mapOf(
"Error Code" to errorCode,
"Source" to source,
),
)
}

View file

@ -1,6 +1,8 @@
package com.tangem.feature.wallet.child.wallet.model.intents
import arrow.core.getOrElse
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
@ -40,13 +42,15 @@ internal class VisaWalletIntentsImplementor @Inject constructor(
private val getUserWalletsUseCase: GetWalletsUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
) : BaseWalletClickIntents(), VisaWalletIntents {
private val balancesAndLimitsBottomSheetConverter by lazy(mode = LazyThreadSafetyMode.NONE) {
BalancesAndLimitsBottomSheetConverter(eventSender)
BalancesAndLimitsBottomSheetConverter(eventSender, analyticsEventHandler)
}
override fun onBalancesAndLimitsClick() {
analyticsEventHandler.send(MainScreenAnalyticsEvent.LimitsClicked)
modelScope.launch(dispatchers.main) {
val userWalletId = stateController.getSelectedWalletId()
val balancesAndLimits = getVisaCurrencyUseCase(userWalletId)
@ -87,6 +91,7 @@ internal class VisaWalletIntentsImplementor @Inject constructor(
}
override fun onExploreClick(exploreUrl: String) {
analyticsEventHandler.send(MainScreenAnalyticsEvent.ButtonExplore)
router.openUrl(exploreUrl)
}

View file

@ -15,6 +15,7 @@ import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetCo
import com.tangem.common.ui.bottomsheet.receive.AddressModel
import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig
import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.WrappedList
@ -78,7 +79,7 @@ interface WalletCurrencyActionsClickIntents {
unavailabilityReason: ScenarioUnavailabilityReason,
)
fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus, event: AnalyticsEvent? = null)
fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?)
@ -94,7 +95,7 @@ interface WalletCurrencyActionsClickIntents {
fun onAnalyticsClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onMultiWalletBuyClick(userWalletId: UserWalletId)
fun onMultiWalletBuyClick(userWalletId: UserWalletId, screenType: String)
fun onMultiWalletSellClick(userWalletId: UserWalletId)
@ -151,7 +152,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
appRouter.push(route)
}
override fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
override fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus, event: AnalyticsEvent?) {
val userWalletId = stateHolder.getSelectedWalletId()
analyticsEventHandler.send(
@ -166,6 +167,8 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
event = TokenReceiveAnalyticsEvent.ReceiveScreenOpened(cryptoCurrencyStatus.currency.symbol),
)
event?.let { analyticsEventHandler.send(it) }
stateHolder.showBottomSheet(
createReceiveBottomSheetContent(
currency = cryptoCurrencyStatus.currency,
@ -470,7 +473,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
}
override fun onMultiWalletBuyClick(userWalletId: UserWalletId) {
override fun onMultiWalletBuyClick(userWalletId: UserWalletId, screenType: String) {
onMultiWalletActionClick(
statusFlow = if (onrampFeatureToggles.isFeatureEnabled) {
rampStateManager.getExpressInitializationStatus(userWalletId)
@ -478,7 +481,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
rampStateManager.getBuyInitializationStatus()
},
route = AppRoute.BuyCrypto(userWalletId = userWalletId),
eventCreator = MainScreenAnalyticsEvent::ButtonBuy,
eventCreator = { MainScreenAnalyticsEvent.ButtonBuy(status = it, screenType = screenType) },
)
}

View file

@ -2,6 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent.Companion.VISA_TYPE
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
@ -144,17 +146,23 @@ internal class SetVisaInfoTransformer(
),
)
return return persistentListOf(
return persistentListOf(
WalletManageButton.Receive(
enabled = true,
dimContent = false,
onClick = { clickIntents.onReceiveClick(cryptoCurrencyStatus = cryptoCurrencyStatus) },
onLongClick = { clickIntents.onCopyAddressLongClick(cryptoCurrencyStatus = cryptoCurrencyStatus) },
onClick = {
clickIntents.onReceiveClick(cryptoCurrencyStatus, MainScreenAnalyticsEvent.ButtonReceive)
},
onLongClick = {
clickIntents.onCopyAddressLongClick(cryptoCurrencyStatus)
},
),
WalletManageButton.Buy(
enabled = true,
dimContent = false,
onClick = { clickIntents.onMultiWalletBuyClick(userWalletId = userWallet.walletId) },
onClick = {
clickIntents.onMultiWalletBuyClick(userWalletId = userWallet.walletId, screenType = VISA_TYPE)
},
),
)
}

View file

@ -1,5 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.DateTimeFormatters
@ -13,6 +15,7 @@ import java.math.BigDecimal
internal class BalancesAndLimitsBottomSheetConverter(
private val eventSender: WalletEventSender,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Converter<VisaCurrency, BalancesAndLimitsBottomSheetConfig> {
override fun convert(value: VisaCurrency): BalancesAndLimitsBottomSheetConfig {
@ -28,23 +31,25 @@ internal class BalancesAndLimitsBottomSheetConverter(
blockedBalance = value.balances.blocked.let(::formatAmount),
debit = value.balances.debt.let(::formatAmount),
amlVerified = value.balances.verified.let(::formatAmount),
onInfoClick = this::showBalanceInfo,
onInfoClick = this::balanceInfoOnClick,
),
limit = BalancesAndLimitsBottomSheetConfig.Limit(
availableBy = DateTimeFormatters.formatDate(date = value.limits.expirationDate),
total = otpLimit,
other = noOtpLimit,
singleTransaction = value.limits.singleTransaction.let(::formatAmount),
onInfoClick = { showLimitInfo(otpLimit, noOtpLimit) },
onInfoClick = { limitInfoOnClick(otpLimit, noOtpLimit) },
),
)
}
private fun showBalanceInfo() {
private fun balanceInfoOnClick() {
analyticsEventHandler.send(MainScreenAnalyticsEvent.NoticeBalancesInfo)
eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaBalancesInfo))
}
private fun showLimitInfo(totalLimit: String, otherLimit: String) {
private fun limitInfoOnClick(totalLimit: String, otherLimit: String) {
analyticsEventHandler.send(MainScreenAnalyticsEvent.NoticeLimitsInfo)
eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaLimitsInfo(totalLimit, otherLimit)))
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.state.utils
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent.Companion.WALLET_TYPE
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
@ -103,7 +104,7 @@ internal class WalletLoadingStateFactory(
WalletManageButton.Buy(
enabled = true,
dimContent = false,
onClick = { clickIntents.onMultiWalletBuyClick(userWalletId = userWallet.walletId) },
onClick = { clickIntents.onMultiWalletBuyClick(userWalletId = userWallet.walletId, WALLET_TYPE) },
),
WalletManageButton.Swap(
enabled = true,