Updated on 2026-08-14
This commit is contained in:
parent
b6585c69a9
commit
850bc92749
11 changed files with 336 additions and 29 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<SendViewModel>()
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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<String, String> = 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"),
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<FragmentManager>,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val isEditingDisabled: Boolean,
|
||||
) {
|
||||
private var mutableCurrentState: MutableStateFlow<SendUiStateType> = 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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue