Updated on 2026-08-14
This commit is contained in:
commit
68ee4b3c00
316 changed files with 7635 additions and 1625 deletions
|
|
@ -1,9 +1,16 @@
|
|||
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
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.visa.GetVisaCurrencyUseCase
|
||||
import com.tangem.domain.visa.GetVisaTxDetailsUseCase
|
||||
import com.tangem.domain.visa.model.VisaTxDetails
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.BalancesAndLimitsBottomSheetConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.VisaTxDetailsBottomSheetConverter
|
||||
|
|
@ -20,22 +27,30 @@ internal interface VisaWalletIntents {
|
|||
fun onVisaTransactionClick(id: String)
|
||||
|
||||
fun onExploreClick(exploreUrl: String)
|
||||
|
||||
fun onDisputeClick(txDetails: VisaTxDetails)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class VisaWalletIntentsImplementor @Inject constructor(
|
||||
private val stateController: WalletStateController,
|
||||
private val eventSender: WalletEventSender,
|
||||
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,
|
||||
private val getVisaTxDetailsUseCase: GetVisaTxDetailsUseCase,
|
||||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
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)
|
||||
|
|
@ -76,6 +91,23 @@ internal class VisaWalletIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onExploreClick(exploreUrl: String) {
|
||||
analyticsEventHandler.send(MainScreenAnalyticsEvent.ButtonExplore)
|
||||
router.openUrl(exploreUrl)
|
||||
}
|
||||
|
||||
override fun onDisputeClick(txDetails: VisaTxDetails) {
|
||||
modelScope.launch {
|
||||
val userWalletId = stateController.getSelectedWalletId()
|
||||
val userWallet = getUserWalletsUseCase.invokeSync()
|
||||
.firstOrNull { it.walletId == userWalletId } ?: return@launch
|
||||
val cardInfo = getCardInfoUseCase.invoke(userWallet.scanResponse).getOrNull() ?: return@launch
|
||||
|
||||
sendFeedbackEmailUseCase(
|
||||
FeedbackEmailType.Visa.Dispute(
|
||||
cardInfo = cardInfo,
|
||||
visaTxDetails = txDetails,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -240,6 +240,6 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onNFTClick(userWalletId: UserWalletId) {
|
||||
router.openNFTCollectionsScreen(userWalletId)
|
||||
router.openNFT(userWalletId)
|
||||
}
|
||||
}
|
||||
|
|
@ -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) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
reduxStateHolder.dispatchDialogShow(StateDialog.ScanFailsDialog(StateDialog.ScanFailsSource.MAIN, onTryAgain))
|
||||
}
|
||||
|
||||
override fun openNFTCollectionsScreen(userWalletId: UserWalletId) {
|
||||
router.push(AppRoute.NFTCollections(userWalletId))
|
||||
override fun openNFT(userWalletId: UserWalletId) {
|
||||
router.push(AppRoute.NFT(userWalletId))
|
||||
}
|
||||
}
|
||||
|
|
@ -58,5 +58,5 @@ internal interface InnerWalletRouter {
|
|||
fun openScanFailedDialog(onTryAgain: () -> Unit)
|
||||
|
||||
/** Open NFT collections screen */
|
||||
fun openNFTCollectionsScreen(userWalletId: UserWalletId)
|
||||
fun openNFT(userWalletId: UserWalletId)
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
internal data class VisaTxDetailsBottomSheetConfig(
|
||||
val transaction: Transaction,
|
||||
val requests: ImmutableList<Request>,
|
||||
val onDisputeClick: () -> Unit,
|
||||
) : TangemBottomSheetConfigContent {
|
||||
|
||||
data class Transaction(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ internal class VisaTxDetailsBottomSheetConverter(
|
|||
return VisaTxDetailsBottomSheetConfig(
|
||||
transaction = createTransaction(value),
|
||||
requests = value.requests.map(::createRequest).toImmutableList(),
|
||||
onDisputeClick = { clickIntents.onDisputeClick(value) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.SecondaryButtonIconStart
|
||||
import com.tangem.core.ui.components.SpacerW12
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
|
|
@ -36,26 +38,45 @@ internal fun VisaTxDetailsBottomSheet(config: TangemBottomSheetConfig) {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun VisaTxDetailsBottomSheetContent(config: VisaTxDetailsBottomSheetConfig, modifier: Modifier = Modifier) {
|
||||
ContentContainer(
|
||||
modifier = modifier,
|
||||
blocksCount = config.requests.size.inc(),
|
||||
title = {
|
||||
Column {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size44)
|
||||
.background(TangemTheme.colors.background.secondary),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.visa_transaction_details_header),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
},
|
||||
block = { index ->
|
||||
if (index == 0) {
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier.background(TangemTheme.colors.background.secondary),
|
||||
contentPadding = PaddingValues(
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
item {
|
||||
TransactionBlock(config.transaction)
|
||||
} else {
|
||||
BlockchainRequestBlock(config.requests[index - 1])
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
items(config.requests) { item ->
|
||||
BlockchainRequestBlock(item)
|
||||
}
|
||||
|
||||
item {
|
||||
DisputeButton(config.onDisputeClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -173,38 +194,14 @@ private fun BlockchainRequestBlock(request: VisaTxDetailsBottomSheetConfig.Reque
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun ContentContainer(
|
||||
blocksCount: Int,
|
||||
title: @Composable BoxScope.() -> Unit,
|
||||
block: @Composable ColumnScope.(Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier.background(TangemTheme.colors.background.secondary),
|
||||
contentPadding = PaddingValues(
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
stickyHeader {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size44)
|
||||
.background(TangemTheme.colors.background.secondary),
|
||||
contentAlignment = Alignment.Center,
|
||||
content = title,
|
||||
)
|
||||
}
|
||||
items(blocksCount) { index ->
|
||||
Column {
|
||||
block(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun DisputeButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
SecondaryButtonIconStart(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
text = stringResourceSafe(R.string.visa_tx_dispute_button),
|
||||
iconResId = R.drawable.ic_alert_triangle_20,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
|
|
@ -249,20 +246,8 @@ private class VisaTxDetailsBottomSheetParameterProvider :
|
|||
txStatus = "confirmed",
|
||||
onExploreClick = {},
|
||||
),
|
||||
VisaTxDetailsBottomSheetConfig.Request(
|
||||
id = "524582128501966799",
|
||||
type = "settlement",
|
||||
status = "accepted",
|
||||
blockchainAmount = "1.0614 USDT",
|
||||
transactionAmount = "0.99 €",
|
||||
currencyCode = "978",
|
||||
errorCode = 0,
|
||||
date = "2023-12-01 00:01:00.000 +0300",
|
||||
txHash = "0x635841d5fbdf1087cdd929019c863ee88a7165e4340bc17ddd0b1d04dfb11daa",
|
||||
txStatus = "confirmed",
|
||||
onExploreClick = {},
|
||||
),
|
||||
),
|
||||
onDisputeClick = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue