Updated on 2026-08-14
This commit is contained in:
parent
2a1369fce6
commit
5a99de87fb
29 changed files with 194 additions and 47 deletions
|
|
@ -14,9 +14,15 @@ interface SendComponent : ComposableContentComponent {
|
|||
val amount: String? = null,
|
||||
val tag: String? = null,
|
||||
val destinationAddress: String? = null,
|
||||
val entryType: EntryType = EntryType.Manual,
|
||||
val callback: ModelCallback? = null,
|
||||
)
|
||||
|
||||
enum class EntryType {
|
||||
Manual,
|
||||
QR,
|
||||
}
|
||||
|
||||
interface Factory : ComponentFactory<Params, SendComponent>
|
||||
|
||||
interface ModelCallback {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
|
|||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TYPE
|
||||
import com.tangem.core.analytics.models.AppsFlyerIncludedEvent
|
||||
|
||||
/**
|
||||
|
|
@ -32,11 +33,13 @@ sealed class CommonSendAnalyticEvents(
|
|||
data class AmountScreenOpened(
|
||||
val categoryName: String,
|
||||
val source: CommonSendSource,
|
||||
val type: SendEntryType = SendEntryType.Manual,
|
||||
) : CommonSendAnalyticEvents(
|
||||
category = categoryName,
|
||||
event = "Amount Screen Opened",
|
||||
params = mapOf(
|
||||
SOURCE to source.analyticsName,
|
||||
TYPE to type.analyticsName,
|
||||
),
|
||||
), AppsFlyerIncludedEvent
|
||||
|
||||
|
|
@ -95,6 +98,7 @@ sealed class CommonSendAnalyticEvents(
|
|||
val toDerivationIndex: Int?,
|
||||
val sendBlockchain: String,
|
||||
val sendToken: String,
|
||||
val type: SendEntryType = SendEntryType.Manual,
|
||||
) : CommonSendAnalyticEvents(
|
||||
category = categoryName,
|
||||
event = "Confirm Screen Opened",
|
||||
|
|
@ -108,6 +112,7 @@ sealed class CommonSendAnalyticEvents(
|
|||
"$fromDerivationIndex, $toDerivationIndex",
|
||||
)
|
||||
}
|
||||
put(TYPE, type.analyticsName)
|
||||
},
|
||||
), AppsFlyerIncludedEvent
|
||||
|
||||
|
|
@ -221,6 +226,11 @@ sealed class CommonSendAnalyticEvents(
|
|||
Confirm,
|
||||
}
|
||||
|
||||
enum class SendEntryType(val analyticsName: String) {
|
||||
QR("QR"),
|
||||
Manual("Manually"),
|
||||
}
|
||||
|
||||
enum class CommonSendSource(val analyticsName: String) {
|
||||
Send("Send"),
|
||||
Swap("Swap"),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.tangem.features.send.v2.networkselection.model
|
|||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.common.getTotalCryptoAmount
|
||||
import com.tangem.common.getTotalFiatAmount
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.features.send.v2.send.analytics.SendAnalyticEvents
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -44,6 +46,7 @@ import javax.inject.Inject
|
|||
internal class NetworkSelectionModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
|
|
@ -51,6 +54,10 @@ internal class NetworkSelectionModel @Inject constructor(
|
|||
|
||||
private val params: NetworkSelectionComponent.Params = paramsContainer.require()
|
||||
|
||||
init {
|
||||
analyticsEventHandler.send(SendAnalyticEvents.ChooseTokenScreenOpened())
|
||||
}
|
||||
|
||||
private val searchQuery = MutableStateFlow("")
|
||||
private val expandedWallets = MutableStateFlow(
|
||||
params.walletGroups.firstOrNull()
|
||||
|
|
@ -227,7 +234,15 @@ internal class NetworkSelectionModel @Inject constructor(
|
|||
text = cryptoAmount.format { crypto(currency) },
|
||||
isFlickering = isFlickering,
|
||||
),
|
||||
onItemClick = { params.onTokenSelected(context.userWalletId, currency) },
|
||||
onItemClick = {
|
||||
analyticsEventHandler.send(
|
||||
SendAnalyticEvents.TokenSelected(
|
||||
token = currency.symbol,
|
||||
blockchain = currency.network.name,
|
||||
),
|
||||
)
|
||||
params.onTokenSelected(context.userWalletId, currency)
|
||||
},
|
||||
onItemLongClick = null,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
sendToken = fromCurrency.symbol,
|
||||
fromDerivationIndex = fromDerivationIndex,
|
||||
toDerivationIndex = null,
|
||||
type = model.consumeEntryType(),
|
||||
),
|
||||
)
|
||||
if (model.currentRoute.value.isEditMode) {
|
||||
|
|
@ -116,6 +117,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
CommonSendAnalyticEvents.AmountScreenOpened(
|
||||
categoryName = model.analyticCategoryName,
|
||||
source = model.analyticsSendSource,
|
||||
type = model.consumeEntryType(),
|
||||
),
|
||||
)
|
||||
activeComponent.updateState(model.uiState.value.amountUM)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,21 @@ internal sealed class SendAnalyticEvents(
|
|||
},
|
||||
), AppsFlyerIncludedEvent
|
||||
|
||||
class ChooseTokenScreenOpened : SendAnalyticEvents(
|
||||
event = "Choose Token Screen Opened",
|
||||
)
|
||||
|
||||
class TokenSelected(
|
||||
val token: String,
|
||||
val blockchain: String,
|
||||
) : SendAnalyticEvents(
|
||||
event = "Token Selected",
|
||||
params = mapOf(
|
||||
TOKEN_PARAM to token,
|
||||
BLOCKCHAIN to blockchain,
|
||||
),
|
||||
)
|
||||
|
||||
data class ConvertTokenButtonClicked(
|
||||
val token: String,
|
||||
val blockchain: String,
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ internal class SendModel @Inject constructor(
|
|||
private val params: SendComponent.Params = paramsContainer.require()
|
||||
private val cryptoCurrency = params.currency
|
||||
|
||||
private var isEntryTypeConsumed = false
|
||||
|
||||
val analyticCategoryName = CommonSendAnalyticEvents.SEND_CATEGORY
|
||||
val analyticsSendSource = CommonSendAnalyticEvents.CommonSendSource.Send
|
||||
|
||||
|
|
@ -264,6 +266,15 @@ internal class SendModel @Inject constructor(
|
|||
showAlertError()
|
||||
}
|
||||
|
||||
fun consumeEntryType(): CommonSendAnalyticEvents.SendEntryType {
|
||||
if (isEntryTypeConsumed) return CommonSendAnalyticEvents.SendEntryType.Manual
|
||||
isEntryTypeConsumed = true
|
||||
return when (params.entryType) {
|
||||
SendComponent.EntryType.QR -> CommonSendAnalyticEvents.SendEntryType.QR
|
||||
SendComponent.EntryType.Manual -> CommonSendAnalyticEvents.SendEntryType.Manual
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun prepareTransferTransaction(): Either<Throwable, TransactionData> {
|
||||
val predefinedValues = predefinedValues
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ internal class WalletComponent @AssistedInject constructor(
|
|||
address = dialogConfig.address,
|
||||
amount = dialogConfig.amount?.parseBigDecimal(currency.decimals),
|
||||
tag = dialogConfig.memo,
|
||||
entryType = AppRoute.Send.EntryType.Manual,
|
||||
)
|
||||
},
|
||||
onDismiss = model.innerWalletRouter.dialogNavigation::dismiss,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
|
|||
import arrow.core.getOrElse
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
|
|
@ -767,6 +768,7 @@ internal class WalletModel @Inject constructor(
|
|||
userWalletId = stateHolder.getSelectedWalletId(),
|
||||
uri = target.uri,
|
||||
source = source,
|
||||
screen = WcPairRequest.Screen.MAIN,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -777,6 +779,7 @@ internal class WalletModel @Inject constructor(
|
|||
address = target.address,
|
||||
amount = target.amount?.parseBigDecimal(target.currency.decimals),
|
||||
tag = target.memo,
|
||||
entryType = AppRoute.Send.EntryType.QR,
|
||||
)
|
||||
}
|
||||
is QrSendTarget.Multiple -> {
|
||||
|
|
@ -789,9 +792,17 @@ internal class WalletModel @Inject constructor(
|
|||
private fun handleQrError(error: ClassifiedQrContent.Error) {
|
||||
when (error) {
|
||||
is ClassifiedQrContent.Error.Unrecognized -> {
|
||||
analyticsEventsHandler.send(
|
||||
WalletScreenAnalyticsEvent.MainScreen.NoticeUnrecognizedQr(),
|
||||
)
|
||||
uiMessageSender.send(WalletAlertUM.qrCodeUnrecognized())
|
||||
}
|
||||
is ClassifiedQrContent.Error.UnsupportedNetwork -> {
|
||||
analyticsEventsHandler.send(
|
||||
WalletScreenAnalyticsEvent.MainScreen.NoticeNoAvailableTokens(
|
||||
blockchain = error.blockchain,
|
||||
),
|
||||
)
|
||||
uiMessageSender.send(WalletAlertUM.qrCodeUnsupportedNetwork())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -382,6 +382,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onScanQrClick() {
|
||||
analyticsEventHandler.send(MainScreenAnalyticsEvent.ButtonQrScan())
|
||||
router.openQrScanner()
|
||||
}
|
||||
}
|
||||
|
|
@ -186,6 +186,7 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
address: String,
|
||||
amount: String?,
|
||||
tag: String?,
|
||||
entryType: AppRoute.Send.EntryType,
|
||||
) {
|
||||
router.push(
|
||||
AppRoute.Send(
|
||||
|
|
@ -194,6 +195,7 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
destinationAddress = address,
|
||||
amount = amount,
|
||||
tag = tag,
|
||||
entryType = entryType,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,7 +95,14 @@ internal interface InnerWalletRouter {
|
|||
fun openQrScanner()
|
||||
|
||||
/** Open send screen with prefilled destination */
|
||||
fun openSend(userWalletId: UserWalletId, currency: CryptoCurrency, address: String, amount: String?, tag: String?)
|
||||
fun openSend(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
address: String,
|
||||
amount: String?,
|
||||
tag: String?,
|
||||
entryType: AppRoute.Send.EntryType = AppRoute.Send.EntryType.Manual,
|
||||
)
|
||||
|
||||
/** Open network selection bottom sheet for multiple QR matches */
|
||||
fun openNetworkSelectionBottomSheet(target: QrSendTarget.Multiple)
|
||||
|
|
|
|||
|
|
@ -167,6 +167,17 @@ sealed class WalletScreenAnalyticsEvent {
|
|||
|
||||
class NoticeSeedPhraseSupportButtonDeclined : MainScreen(event = "Button - Support Declined")
|
||||
|
||||
class NoticeUnrecognizedQr : MainScreen(
|
||||
event = "Notice - Unrecognized QR",
|
||||
)
|
||||
|
||||
class NoticeNoAvailableTokens(blockchain: String?) : MainScreen(
|
||||
event = "Notice - No Available Tokens",
|
||||
params = buildMap {
|
||||
if (blockchain != null) put("Blockchain", blockchain)
|
||||
},
|
||||
)
|
||||
|
||||
// region Referral Promo
|
||||
class ReferralPromo : MainScreen(event = "Referral Banner")
|
||||
class ReferralPromoButtonParticipate : MainScreen(event = "Button - Referral Participate")
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import com.tangem.core.decompose.navigation.inner.InnerRouter
|
|||
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUM
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletconnect.model.WcPairRequest
|
||||
import com.tangem.features.account.PortfolioSelectorComponent
|
||||
import com.tangem.features.walletconnect.connections.model.WcPairModel
|
||||
|
|
@ -127,5 +126,7 @@ internal class WcPairComponent(
|
|||
}
|
||||
}
|
||||
|
||||
data class Params(val userWalletId: UserWalletId, val wcUrl: String, val source: WcPairRequest.Source)
|
||||
data class Params(
|
||||
val request: WcPairRequest,
|
||||
)
|
||||
}
|
||||
|
|
@ -86,6 +86,7 @@ internal class WcConnectionsModel @Inject constructor(
|
|||
userWalletId = params.userWalletId,
|
||||
uri = result.qrCode,
|
||||
source = source,
|
||||
screen = WcPairRequest.Screen.WALLET_CONNECT,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.domain.walletconnect.model.WcPairError
|
||||
import com.tangem.domain.walletconnect.model.WcPairError.Unknown
|
||||
import com.tangem.domain.walletconnect.model.WcPairRequest
|
||||
import com.tangem.domain.walletconnect.model.WcSessionApprove
|
||||
import com.tangem.domain.walletconnect.model.WcSessionProposal
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
|
||||
|
|
@ -75,13 +74,7 @@ internal class WcPairModel @Inject constructor(
|
|||
) : Model(), WcPairComponentCallback {
|
||||
|
||||
private val params: WcPairComponent.Params = paramsContainer.require()
|
||||
private val wcPairUseCase = wcPairUseCaseFactory.create(
|
||||
WcPairRequest(
|
||||
userWalletId = params.userWalletId,
|
||||
uri = params.wcUrl,
|
||||
source = params.source,
|
||||
),
|
||||
)
|
||||
private val wcPairUseCase = wcPairUseCaseFactory.create(params.request)
|
||||
|
||||
val stackNavigation = StackNavigation<WcAppInfoRoutes>()
|
||||
val portfolioFetcher: PortfolioFetcher = portfolioFetcherFactory.create(
|
||||
|
|
@ -109,7 +102,7 @@ internal class WcPairModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
val portfolioBalance = portfolioFetcher.data.first().balances
|
||||
.firstNotNullOfOrNull { (walletId, balance) ->
|
||||
if (params.userWalletId == walletId) balance else null
|
||||
if (params.request.userWalletId == walletId) balance else null
|
||||
}
|
||||
if (portfolioBalance == null) {
|
||||
router.pop()
|
||||
|
|
|
|||
|
|
@ -89,9 +89,7 @@ internal class DefaultWcRoutingComponent @AssistedInject constructor(
|
|||
appComponentContext = childContext,
|
||||
portfolioSelectorComponentFactory = portfolioSelectorComponentFactory,
|
||||
params = WcPairComponent.Params(
|
||||
userWalletId = config.request.userWalletId,
|
||||
wcUrl = config.request.uri,
|
||||
source = config.request.source,
|
||||
request = config.request,
|
||||
),
|
||||
)
|
||||
is WcInnerRoute.UnsupportedMethodAlert -> AlertsComponent(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue