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 830d2621c2..a684154112 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 @@ -125,6 +125,7 @@ sealed class AnalyticsParam { const val SOURCE = "Source" const val BALANCE = "Balance" const val BATCH = "Batch" + const val TYPE = "Type" const val FEE_TYPE = "Fee Type" const val PERMISSION_TYPE = "Permission Type" const val PRODUCT_TYPE = "Product Type" diff --git a/features/send/impl/build.gradle.kts b/features/send/impl/build.gradle.kts index b35e632634..062932fd25 100644 --- a/features/send/impl/build.gradle.kts +++ b/features/send/impl/build.gradle.kts @@ -45,6 +45,8 @@ dependencies { implementation(projects.core.ui) implementation(projects.core.utils) implementation(projects.core.navigation) + implementation(projects.core.analytics) + implementation(projects.core.analytics.models) /** Common */ implementation(projects.common) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt index c7cbe1a802..1189a49843 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt @@ -8,6 +8,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import arrow.core.getOrElse +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.screen.ComposeFragment @@ -41,6 +42,9 @@ internal class SendFragment : ComposeFragment() { @Inject lateinit var listenToQrScanningUseCase: ListenToQrScanningUseCase + @Inject + lateinit var analyticsEventsHandler: AnalyticsEventHandler + private val viewModel by viewModels() private val innerSendRouter: InnerSendRouter get() = requireNotNull(router as? InnerSendRouter) { @@ -57,6 +61,7 @@ internal class SendFragment : ComposeFragment() { StateRouter( fragmentManager = WeakReference(parentFragmentManager), isEditingDisabled = isEditingDisabled, + analyticsEventsHandler = analyticsEventsHandler, ), ) listenToQrCode() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt new file mode 100644 index 0000000000..b62f028e0a --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt @@ -0,0 +1,136 @@ +package com.tangem.features.send.impl.presentation.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE +import com.tangem.core.analytics.models.AnalyticsParam.Key.TYPE +import com.tangem.core.analytics.models.AnalyticsParam.Key.VALIDATION + +/** + * Send screen analytics + */ +internal sealed class SendAnalyticEvents( + event: String, + params: Map = mapOf(), +) : AnalyticsEvent(category = "Token / Send", event = event, params = params) { + + /** Send screen opened */ + object SendOpened : SendAnalyticEvents(event = "Send Screen Opened") + + /** Next button clicked */ + data class NextButtonClicked(val source: SendScreenSource) : SendAnalyticEvents( + event = "Button - Next", + params = mapOf(SOURCE to source.name), + ) + + /** Back button clicked */ + data class BackButtonClicked(val source: SendScreenSource) : SendAnalyticEvents( + event = "Button - Back", + params = mapOf(SOURCE to source.name), + ) + + // region Address + /** Recipient address screen opened */ + object AddressScreenOpened : SendAnalyticEvents(event = "Address Screen Opened") + + /** Address to send entered */ + data class AddressEntered(val source: EnterAddressSource, val isValid: Boolean) : SendAnalyticEvents( + event = "Address Entered", + params = mapOf( + SOURCE to source.name, + VALIDATION to if (isValid) "Success" else "Fail", + ), + ) + + /** Paste from clipboard button clicked */ + data class PasteButtonClicked(val type: PasteType) : SendAnalyticEvents( + event = "Button - Paste", + params = mapOf( + TYPE to type.name, + ), + ) + + /** Qr Code button clicked */ + object QrCodeButtonClicked : SendAnalyticEvents(event = "Button - QR Code") + // endregion + + // region Amount + /** Amount screen opened */ + object AmountScreenOpened : SendAnalyticEvents(event = "Amount Screen Opened") + + /** Selected currency */ + data class SelectedCurrency(val type: SelectedCurrencyType) : SendAnalyticEvents( + event = "Selected Currency", + params = mapOf(TYPE to type.value), + ) + + /** Currency selector button clicked */ + object SwapCurrencyButtonClicked : SendAnalyticEvents(event = "Button - Swap Currency") + // endregion + + // region Fee + /** Fee screen opened */ + object FeeScreenOpened : SendAnalyticEvents(event = "Fee Screen Opened") + + /** Selected fee (send after next screen opened) */ + data class SelectedFee(val fee: String) : SendAnalyticEvents( + event = "Fee Selected", + params = mapOf("Commission" to fee), + ) + + /** Custom fee selected */ + object CustomFeeButtonClicked : SendAnalyticEvents(event = "Custom Fee Clicked") + + /** Custom fee edited */ + object GasPriceInserter : SendAnalyticEvents(event = "Gas Price Inserted") + + /** Subtract from amount selector switched (send after next screen opened) */ + object SubtractFromAmount : SendAnalyticEvents(event = "Subtract from Amount") + // endregion + + // region Confirmation + /** Confirmation screen opened */ + object ConfirmationScreenOpened : SendAnalyticEvents(event = "Confirm Screen Opened") + + /** Send transaction button clicked */ + object SendButtonClicked : SendAnalyticEvents(event = "Button - Send") + + /** Screen reopened from confirmation screen */ + data class ScreenReopened(val source: SendScreenSource) : SendAnalyticEvents( + event = "Screen Reopened", + params = mapOf(SOURCE to source.name), + ) + // endregion + + // region Transaction Result + /** Transaction send screen opened */ + object TransactionScreenOpened : SendAnalyticEvents(event = "Transaction Sent Screen Opened") + + /** Share button clicked */ + object ShareButtonClicked : SendAnalyticEvents(event = "Button - Share") + + /** Expore button clicked */ + object ExploreButtonClicked : SendAnalyticEvents(event = "Button - Explore") + // endregion +} + +internal enum class SendScreenSource { + Address, + Amount, + Fee, +} + +internal enum class EnterAddressSource { + QRCode, + PasteButton, + RecentAddress, +} + +internal enum class PasteType { + Address, + Memo, +} + +internal enum class SelectedCurrencyType(val value: String) { + Token("Token"), + AppCurrency("App Currency"), +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt new file mode 100644 index 0000000000..e3f5666a1c --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendOnNextScreenAnalyticSender.kt @@ -0,0 +1,44 @@ +package com.tangem.features.send.impl.presentation.analytics.utils + +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.features.send.impl.presentation.analytics.SelectedCurrencyType +import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents +import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.SendUiStateType +import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState +import com.tangem.features.send.impl.presentation.state.fee.FeeType + +internal class SendOnNextScreenAnalyticSender( + private val analyticsEventHandler: AnalyticsEventHandler, +) { + fun send(prevScreen: SendUiStateType, state: SendUiState) { + when (prevScreen) { + SendUiStateType.Fee -> { + val feeState = state.feeState ?: return + val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content + feeSelectorState?.selectedFee?.let { selectedFee -> + val isCustomFeeEdited = feeState.fee?.amount?.value != feeSelectorState.fees.normal.amount.value + if (selectedFee == FeeType.Custom && isCustomFeeEdited) { + analyticsEventHandler.send(SendAnalyticEvents.GasPriceInserter) + } + analyticsEventHandler.send(SendAnalyticEvents.SelectedFee(selectedFee.name)) + } + if (feeState.isSubtract) { + analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount) + } + } + SendUiStateType.Amount -> { + val isFiatSelected = state.amountState?.amountTextField?.isFiatValue ?: return + val selectedCurrency = if (isFiatSelected) { + SelectedCurrencyType.Token + } else { + SelectedCurrencyType.AppCurrency + } + analyticsEventHandler.send( + SendAnalyticEvents.SelectedCurrency(selectedCurrency), + ) + } + else -> Unit + } + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendRecipientAnalyticsSender.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendRecipientAnalyticsSender.kt new file mode 100644 index 0000000000..000f48dea1 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendRecipientAnalyticsSender.kt @@ -0,0 +1,26 @@ +package com.tangem.features.send.impl.presentation.analytics.utils + +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource +import com.tangem.features.send.impl.presentation.analytics.PasteType +import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents + +internal class SendRecipientAnalyticsSender( + private val analyticsEventHandler: AnalyticsEventHandler, +) { + + fun sendAddressAnalytics(type: EnterAddressSource?, isValidAddress: Boolean) { + type?.let { + if (type == EnterAddressSource.PasteButton) { + analyticsEventHandler.send(SendAnalyticEvents.PasteButtonClicked(PasteType.Address)) + } + analyticsEventHandler.send(SendAnalyticEvents.AddressEntered(it, isValidAddress)) + } + } + + fun sendMemoAnalytics(isPasted: Boolean) { + if (isPasted) { + analyticsEventHandler.send(SendAnalyticEvents.PasteButtonClicked(PasteType.Memo)) + } + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt index ea2c456a23..ea62b4700d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt @@ -1,6 +1,9 @@ package com.tangem.features.send.impl.presentation.state import androidx.fragment.app.FragmentManager +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents +import com.tangem.features.send.impl.presentation.analytics.SendScreenSource import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update @@ -8,6 +11,7 @@ import java.lang.ref.WeakReference internal class StateRouter( private val fragmentManager: WeakReference, + private val analyticsEventsHandler: AnalyticsEventHandler, private val isEditingDisabled: Boolean, ) { private var mutableCurrentState: MutableStateFlow = MutableStateFlow( @@ -28,26 +32,51 @@ internal class StateRouter( when { isSuccess -> popBackStack() isEditingDisabled -> when (currentState.value) { - SendUiStateType.Send -> showFee() + SendUiStateType.Send -> { + analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Fee)) + showFee() + } else -> popBackStack() } else -> when (currentState.value) { - SendUiStateType.Amount -> showRecipient() - SendUiStateType.Fee -> showAmount() - SendUiStateType.Send -> showFee() + SendUiStateType.Amount -> { + analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Address)) + showRecipient() + } + SendUiStateType.Fee -> { + analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Amount)) + showAmount() + } + SendUiStateType.Send -> { + analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Fee)) + showFee() + } else -> popBackStack() } } } - fun onNextClick() { + fun onNextClick(): SendUiStateType { + val prevState = currentState.value when (currentState.value) { - SendUiStateType.Recipient -> showAmount() - SendUiStateType.Amount -> showFee() - SendUiStateType.Fee -> showSend() - SendUiStateType.Send -> onBackClick() + SendUiStateType.Recipient -> { + analyticsEventsHandler.send(SendAnalyticEvents.NextButtonClicked(SendScreenSource.Amount)) + showAmount() + } + SendUiStateType.Amount -> { + analyticsEventsHandler.send(SendAnalyticEvents.NextButtonClicked(SendScreenSource.Fee)) + showFee() + } + SendUiStateType.Fee -> { + analyticsEventsHandler.send(SendAnalyticEvents.NextButtonClicked(SendScreenSource.Fee)) + showSend() + } + SendUiStateType.Send -> { + onBackClick() + } else -> popBackStack() } + return prevState } fun onPrevClick() { @@ -55,26 +84,36 @@ internal class StateRouter( popBackStack() } else { when (currentState.value) { - SendUiStateType.Amount -> showRecipient() - SendUiStateType.Fee -> showAmount() + SendUiStateType.Amount -> { + analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Amount)) + showRecipient() + } + SendUiStateType.Fee -> { + analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Fee)) + showAmount() + } else -> popBackStack() } } } fun showAmount() { + analyticsEventsHandler.send(SendAnalyticEvents.AmountScreenOpened) mutableCurrentState.update { SendUiStateType.Amount } } fun showRecipient() { + analyticsEventsHandler.send(SendAnalyticEvents.AddressScreenOpened) mutableCurrentState.update { SendUiStateType.Recipient } } fun showFee() { + analyticsEventsHandler.send(SendAnalyticEvents.FeeScreenOpened) mutableCurrentState.update { SendUiStateType.Fee } } private fun showSend() { + analyticsEventsHandler.send(SendAnalyticEvents.ConfirmationScreenOpened) mutableCurrentState.update { SendUiStateType.Send } } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index 064ec52822..fb4fa79a9f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -110,6 +110,7 @@ private fun SendPrimaryNavigationButton(uiState: SendUiState, modifier: Modifier textRes = textId, txUrl = txUrl, onExploreClick = { uiState.clickIntents.onExploreClick(txUrl) }, + onShareClick = uiState.clickIntents::onShareClick, onDoneClick = buttonClick, modifier = Modifier, ) @@ -130,6 +131,7 @@ private fun PrimaryButtonsDone( @StringRes textRes: Int, txUrl: String, onExploreClick: () -> Unit, + onShareClick: () -> Unit, onDoneClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -152,6 +154,7 @@ private fun PrimaryButtonsDone( onClick = { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) context.shareText(txUrl) + onShareClick() }, modifier = Modifier.weight(1f), ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index 3fa19bdf16..c80581a8f8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -24,6 +24,7 @@ import com.tangem.core.ui.components.inputrow.InputRowRecipient import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.R +import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.ui.common.FooterContainer @@ -58,7 +59,7 @@ internal fun SendRecipientContent( title = address.label, placeholder = address.placeholder, onValueChange = address.onValueChange, - onPasteClick = clickIntents::onRecipientAddressValueChange, + onPasteClick = { clickIntents.onRecipientAddressValueChange(it, EnterAddressSource.PasteButton) }, isError = isError, isLoading = isValidating, error = address.error, @@ -80,7 +81,7 @@ internal fun SendRecipientContent( placeholder = placeholder, footer = stringResource(R.string.send_recipient_memo_footer), onValueChange = memoField.onValueChange, - onPasteClick = clickIntents::onRecipientMemoValueChange, + onPasteClick = { clickIntents.onRecipientMemoValueChange(it, isPasted = true) }, modifier = Modifier.padding(top = TangemTheme.dimens.spacing20), isError = memoField.isError, error = memoField.error, @@ -165,7 +166,9 @@ private fun LazyListScope.recipientListItem( }, ) .background(TangemTheme.colors.background.action), - onClick = { clickIntents.onRecipientAddressValueChange(title) }, + onClick = { + clickIntents.onRecipientAddressValueChange(title, EnterAddressSource.RecentAddress) + }, ) } } @@ -203,7 +206,7 @@ private fun RecipientWalletListItem( ListItemWithIcon( title = wallet.title.resolveReference(), subtitle = wallet.subtitle.resolveReference(), - onClick = { clickIntents.onRecipientAddressValueChange(title) }, + onClick = { clickIntents.onRecipientAddressValueChange(title, EnterAddressSource.RecentAddress) }, ) } if (!item.isWalletsOnly) { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index cd3c448c3f..18252b0ed4 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -2,10 +2,11 @@ package com.tangem.features.send.impl.presentation.viewmodel import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.state.fee.FeeType @Suppress("TooManyFunctions") -interface SendClickIntents { +internal interface SendClickIntents { fun popBackStack() @@ -30,9 +31,9 @@ interface SendClickIntents { // endregion // region Recipient - fun onRecipientAddressValueChange(value: String) + fun onRecipientAddressValueChange(value: String, type: EnterAddressSource? = null) - fun onRecipientMemoValueChange(value: String) + fun onRecipientMemoValueChange(value: String, isPasted: Boolean = false) // endregion // region Fee @@ -56,6 +57,8 @@ interface SendClickIntents { fun onExploreClick(txUrl: String) + fun onShareClick() + fun onAmountReduceClick(reducedAmount: String) fun onAmountReduceIgnoreClick() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index d6e32da750..fa912092d9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -9,6 +9,7 @@ import arrow.core.Either import arrow.core.getOrElse import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase @@ -34,6 +35,11 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.* import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.send.impl.navigation.InnerSendRouter +import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource +import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents +import com.tangem.features.send.impl.presentation.analytics.SendScreenSource +import com.tangem.features.send.impl.presentation.analytics.utils.SendOnNextScreenAnalyticSender +import com.tangem.features.send.impl.presentation.analytics.utils.SendRecipientAnalyticsSender import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.state.* import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactory @@ -75,6 +81,7 @@ internal class SendViewModel @Inject constructor( private val reduxStateHolder: ReduxStateHolder, private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, + private val analyticsEventHandler: AnalyticsEventHandler, currencyChecksRepository: CurrencyChecksRepository, isFeeApproximateUseCase: IsFeeApproximateUseCase, getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, @@ -147,6 +154,12 @@ internal class SendViewModel @Inject constructor( clickIntents = this, ) + private val sendOnNextScreenAnalyticSender by lazy(LazyThreadSafetyMode.NONE) { + SendOnNextScreenAnalyticSender(analyticsEventHandler) + } + private val sendRecipientAnalyticsSender by lazy(LazyThreadSafetyMode.NONE) { + SendRecipientAnalyticsSender(analyticsEventHandler) + } // todo convert to StateFlow var uiState: SendUiState by mutableStateOf(stateFactory.getInitialState()) private set @@ -170,6 +183,7 @@ internal class SendViewModel @Inject constructor( subscribeOnCurrencyStatusUpdates(owner) onStateActive() subscribeOnBalanceHidden(owner) + analyticsEventHandler.send(SendAnalyticEvents.SendOpened) } fun setRouter(router: InnerSendRouter, stateRouter: StateRouter) { @@ -273,11 +287,11 @@ internal class SendViewModel @Inject constructor( if (transactionId != null && amount != null && destinationAddress != null) { uiState = stateFactory.getReadyState(amount, destinationAddress) - showFee() + stateRouter.showFee() } else { getWalletsAndRecent() uiState = stateFactory.getReadyState() - showRecipient() + stateRouter.showRecipient() } updateNotifications() } @@ -392,10 +406,17 @@ internal class SendViewModel @Inject constructor( // region screen state navigation override fun popBackStack() = stateRouter.popBackStack() override fun onBackClick() = stateRouter.onBackClick(uiState.sendState.isSuccess) - override fun onNextClick() = stateRouter.onNextClick() + override fun onNextClick() { + val prevScreen = stateRouter.onNextClick() + sendOnNextScreenAnalyticSender.send(prevScreen, uiState) + } + override fun onPrevClick() = stateRouter.onPrevClick() - override fun onQrCodeScanClick() = innerRouter.openQrCodeScanner(cryptoCurrency.network.name) + override fun onQrCodeScanClick() { + analyticsEventHandler.send(SendAnalyticEvents.QrCodeButtonClicked) + innerRouter.openQrCodeScanner(cryptoCurrency.network.name) + } override fun onFailedTxEmailClick(errorMessage: String) { reduxStateHolder.dispatch(LegacyAction.SendEmailTransactionFailed(errorMessage)) @@ -407,6 +428,7 @@ internal class SendViewModel @Inject constructor( // region amount state clicks override fun onCurrencyChangeClick(isFiat: Boolean) { + analyticsEventHandler.send(SendAnalyticEvents.SwapCurrencyButtonClicked) uiState = amountStateFactory.getOnCurrencyChangedState(isFiat) } @@ -424,7 +446,7 @@ internal class SendViewModel @Inject constructor( viewModelScope.launch(dispatchers.main) { parseSharedAddressUseCase(address, cryptoCurrency.network).fold( ifRight = { parsedCode -> - onRecipientAddressValueChange(parsedCode.address) + onRecipientAddressValueChange(parsedCode.address, EnterAddressSource.QRCode) parsedCode.amount?.let { onAmountValueChange(it.toPlainString()) } parsedCode.memo?.let { onRecipientMemoValueChange(it) } }, @@ -435,24 +457,26 @@ internal class SendViewModel @Inject constructor( }.saveIn(qrScannerJobHolder) } - override fun onRecipientAddressValueChange(value: String) { + override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) { viewModelScope.launch(dispatchers.main) { if (!checkIfXrpAddressValue(value)) { uiState = stateFactory.onRecipientAddressValueChange(value) uiState = stateFactory.getOnRecipientAddressValidationStarted() val isValidAddress = validateAddress(value) uiState = stateFactory.getOnRecipientAddressValidState(value, isValidAddress) + sendRecipientAnalyticsSender.sendAddressAnalytics(type, isValidAddress) } }.saveIn(addressValidationJobHolder) } - override fun onRecipientMemoValueChange(value: String) { + override fun onRecipientMemoValueChange(value: String, isPasted: Boolean) { viewModelScope.launch(dispatchers.main) { if (!checkIfXrpAddressValue(value)) { uiState = stateFactory.getOnRecipientMemoValueChange(value) uiState = stateFactory.getOnRecipientAddressValidationStarted() val isValidAddress = validateAddress(uiState.recipientState?.addressTextField?.value.orEmpty()) uiState = stateFactory.getOnRecipientMemoValidState(value, isValidAddress) + sendRecipientAnalyticsSender.sendMemoAnalytics(isPasted) } }.saveIn(addressValidationJobHolder) } @@ -482,6 +506,9 @@ internal class SendViewModel @Inject constructor( override fun onFeeSelectorClick(feeType: FeeType) { uiState = feeStateFactory.onFeeSelectedState(feeType) updateFeeNotifications() + if (feeType == FeeType.Custom) { + analyticsEventHandler.send(SendAnalyticEvents.CustomFeeButtonClicked) + } } override fun onCustomFeeValueChange(index: Int, value: String) { @@ -542,15 +569,32 @@ internal class SendViewModel @Inject constructor( onCheckFeeUpdate() } sendIdleTimer = System.currentTimeMillis() + analyticsEventHandler.send(SendAnalyticEvents.SendButtonClicked) } - override fun showAmount() = stateRouter.showAmount() + override fun showAmount() { + stateRouter.showAmount() + analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Amount)) + } - override fun showRecipient() = stateRouter.showRecipient() + override fun showRecipient() { + stateRouter.showRecipient() + analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Address)) + } - override fun showFee() = stateRouter.showFee() + override fun showFee() { + stateRouter.showFee() + analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Fee)) + } - override fun onExploreClick(txUrl: String) = innerRouter.openUrl(txUrl) + override fun onExploreClick(txUrl: String) { + analyticsEventHandler.send(SendAnalyticEvents.ExploreButtonClicked) + innerRouter.openUrl(txUrl) + } + + override fun onShareClick() { + analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked) + } override fun onAmountReduceClick(reducedAmount: String) { uiState = amountStateFactory.getOnAmountValueChange(reducedAmount) @@ -616,6 +660,7 @@ internal class SendViewModel @Inject constructor( uiState = stateFactory.getSendingStateUpdate(isSending = false) uiState = stateFactory.getTransactionSendState(txData) scheduleBalanceUpdate() + analyticsEventHandler.send(SendAnalyticEvents.TransactionScreenOpened) }, ) }