Updated on 2026-08-14
This commit is contained in:
parent
47f9a36fb4
commit
529527cad0
13 changed files with 659 additions and 6 deletions
|
|
@ -103,7 +103,7 @@ internal class ExpressStatusFactory @AssistedInject constructor(
|
|||
}
|
||||
val expressTxsToDisplay = expressTxs.filterNot {
|
||||
when (it) {
|
||||
is ExpressTransactionStateUM.OnrampUM -> false // it.activeStatus.isHidden
|
||||
is ExpressTransactionStateUM.OnrampUM -> it.activeStatus.isHidden
|
||||
else -> false
|
||||
}
|
||||
}.toPersistentList()
|
||||
|
|
|
|||
|
|
@ -52,18 +52,19 @@ dependencies {
|
|||
implementation(projects.core.utils)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.common.routing)
|
||||
implementation(projects.core.deepLinks)
|
||||
implementation(projects.core.deepLinks.global)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.datasource)
|
||||
|
||||
|
||||
implementation(projects.libs.crypto)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.tokens)
|
||||
|
|
@ -82,6 +83,7 @@ dependencies {
|
|||
implementation(projects.domain.markets.models)
|
||||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.onramp.models)
|
||||
implementation(projects.domain.onramp)
|
||||
|
||||
//TODO: Create api/impl modules for onboarding [REDACTED_JIRA]
|
||||
implementation(projects.features.onboarding)
|
||||
|
|
@ -100,6 +102,7 @@ dependencies {
|
|||
|
||||
/** Common modules */
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.common.routing)
|
||||
|
||||
/** Test libraries */
|
||||
implementation(deps.test.junit)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.datasource.local.swaptx.ExpressAnalyticsStatus
|
||||
import com.tangem.domain.onramp.GetOnrampStatusUseCase
|
||||
import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase
|
||||
import com.tangem.domain.onramp.OnrampUpdateTransactionStatusUseCase
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import com.tangem.domain.onramp.model.OnrampStatus.Status.*
|
||||
import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ViewModelScoped
|
||||
internal class OnrampStatusFactory @Inject constructor(
|
||||
private val stateHolder: WalletStateController,
|
||||
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
|
||||
private val getOnrampStatusUseCase: GetOnrampStatusUseCase,
|
||||
private val onrampUpdateTransactionStatusUseCase: OnrampUpdateTransactionStatusUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
suspend fun removeTransactionOnBottomSheetClosed() {
|
||||
val state = stateHolder.getSelectedWallet()
|
||||
val bottomSheetConfig = state.bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig ?: return
|
||||
val selectedTx = bottomSheetConfig.value as? ExpressTransactionStateUM.OnrampUM ?: return
|
||||
|
||||
if (selectedTx.activeStatus.isTerminal) {
|
||||
onrampRemoveTransactionUseCase(externalTxId = selectedTx.info.txExternalId)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateOnrmapTransactionStatuses() = withContext(dispatchers.io) {
|
||||
val singleWalletState = stateHolder.getSelectedWallet() as? WalletState.SingleCurrency.Content
|
||||
?: return@withContext
|
||||
|
||||
singleWalletState.expressTxs.map { tx ->
|
||||
async {
|
||||
if (tx is ExpressTransactionStateUM.OnrampUM) {
|
||||
updateOnrampTxStatus(tx)
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
}
|
||||
|
||||
private suspend fun updateOnrampTxStatus(onrampTx: ExpressTransactionStateUM.OnrampUM) {
|
||||
if (!onrampTx.activeStatus.isTerminal) {
|
||||
getOnrampStatusUseCase(onrampTx.info.txId).fold(
|
||||
ifLeft = {
|
||||
Timber.e("Couldn't update onramp status. $it")
|
||||
},
|
||||
ifRight = { statusModel ->
|
||||
val externalTxId = statusModel.externalTxId
|
||||
val status = toAnalyticStatus(statusModel.status) ?: return
|
||||
|
||||
if (statusModel.status != onrampTx.activeStatus) {
|
||||
analyticsEventHandler.send(
|
||||
TokenOnrampAnalyticsEvent.OnrampStatusChanged(
|
||||
tokenSymbol = onrampTx.info.toAmountSymbol,
|
||||
status = status.name,
|
||||
provider = onrampTx.providerName,
|
||||
fiatCurrency = onrampTx.fromCurrencyCode,
|
||||
),
|
||||
)
|
||||
onrampUpdateTransactionStatusUseCase(externalTxId = externalTxId, statusModel.status)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toAnalyticStatus(status: OnrampStatus.Status?): ExpressAnalyticsStatus? {
|
||||
return when (status) {
|
||||
Expired,
|
||||
Paused,
|
||||
-> ExpressAnalyticsStatus.Cancelled
|
||||
Created,
|
||||
WaitingForPayment,
|
||||
PaymentProcessing,
|
||||
Paid,
|
||||
Sending,
|
||||
-> ExpressAnalyticsStatus.InProgress
|
||||
Verifying -> ExpressAnalyticsStatus.KYC
|
||||
Failed -> ExpressAnalyticsStatus.Fail
|
||||
Finished -> ExpressAnalyticsStatus.Done
|
||||
null -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
|||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.onramp.GetOnrampTransactionsUseCase
|
||||
import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase
|
||||
import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
|
|
@ -27,6 +29,8 @@ internal class SingleWalletContentLoader(
|
|||
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase,
|
||||
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
|
@ -55,6 +59,16 @@ internal class SingleWalletContentLoader(
|
|||
getSingleWalletWarningsFactory = getSingleWalletWarningsFactory,
|
||||
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
|
||||
),
|
||||
SingleWalletExpressStatusesSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
clickIntents = clickIntents,
|
||||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase,
|
||||
getOnrampTransactionsUseCase = getOnrampTransactionsUseCase,
|
||||
onrampRemoveTransactionUseCase = onrampRemoveTransactionUseCase,
|
||||
),
|
||||
TxHistorySubscriber(
|
||||
userWallet = userWallet,
|
||||
isRefresh = isRefresh,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
|||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.onramp.GetOnrampTransactionsUseCase
|
||||
import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase
|
||||
import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
|
|
@ -26,6 +28,8 @@ internal class SingleWalletContentLoaderFactory @Inject constructor(
|
|||
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase,
|
||||
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
|
||||
) {
|
||||
|
|
@ -45,6 +49,8 @@ internal class SingleWalletContentLoaderFactory @Inject constructor(
|
|||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
|
||||
getOnrampTransactionsUseCase = getOnrampTransactionsUseCase,
|
||||
onrampRemoveTransactionUseCase = onrampRemoveTransactionUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
|
|
@ -59,6 +60,8 @@ internal sealed interface WalletState : WalletStateHolder {
|
|||
override val buttons: PersistentList<WalletManageButton>,
|
||||
override val marketPriceBlockState: MarketPriceBlockState,
|
||||
override val txHistoryState: TxHistoryState,
|
||||
val expressTxsToDisplay: PersistentList<ExpressTransactionStateUM>,
|
||||
val expressTxs: PersistentList<ExpressTransactionStateUM>,
|
||||
) : SingleCurrency()
|
||||
|
||||
data class Locked(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.SingleWalletOnrampTransactionConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SetExpressStatusesTransformer(
|
||||
userWalletId: UserWalletId,
|
||||
private val onrampTxs: List<OnrampTransaction>,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.SingleCurrency.Content -> {
|
||||
val expressTxs = SingleWalletOnrampTransactionConverter(
|
||||
clickIntents = clickIntents,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
appCurrency = appCurrency,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
).convertList(onrampTxs).toPersistentList()
|
||||
|
||||
val expressTxsToDisplay = expressTxs.filterNot {
|
||||
it.activeStatus.isHidden
|
||||
}.toPersistentList()
|
||||
|
||||
val expressBottomSheet = prevState.bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig
|
||||
val currentTx = expressTxs.firstOrNull { it.info.txId == expressBottomSheet?.value?.info?.txId }
|
||||
|
||||
prevState.copy(
|
||||
expressTxs = expressTxs,
|
||||
expressTxsToDisplay = expressTxsToDisplay,
|
||||
bottomSheetConfig = prevState.bottomSheetConfig?.updateStateWithExpressStatusBottomSheet(currentTx),
|
||||
)
|
||||
}
|
||||
is WalletState.SingleCurrency.Locked -> {
|
||||
Timber.w("Impossible to load express statuses for locked wallet")
|
||||
prevState
|
||||
}
|
||||
is WalletState.Visa -> {
|
||||
Timber.w("Impossible to load express statuses for visa wallet")
|
||||
prevState
|
||||
}
|
||||
is WalletState.MultiCurrency -> {
|
||||
Timber.w("Impossible to load express statuses for multi-currency wallet")
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TangemBottomSheetConfig.updateStateWithExpressStatusBottomSheet(
|
||||
expressState: ExpressTransactionStateUM?,
|
||||
): TangemBottomSheetConfig {
|
||||
val currentConfig = this.content as? ExpressStatusBottomSheetConfig ?: return this
|
||||
if (expressState == null) return this
|
||||
return copy(
|
||||
content = if (currentConfig.value != expressState) {
|
||||
ExpressStatusBottomSheetConfig(expressState)
|
||||
} else {
|
||||
currentConfig
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
|
||||
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressLinkUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
|
||||
import com.tangem.common.ui.notifications.ExpressNotificationsUM
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal class SingleWalletOnrampTransactionConverter(
|
||||
private val clickIntents: WalletClickIntents,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : Converter<OnrampTransaction, ExpressTransactionStateUM.OnrampUM> {
|
||||
|
||||
private val iconStateConverter = CryptoCurrencyToIconStateConverter()
|
||||
|
||||
private val currency = cryptoCurrencyStatus.currency
|
||||
private val status = cryptoCurrencyStatus.value
|
||||
|
||||
override fun convert(value: OnrampTransaction): ExpressTransactionStateUM.OnrampUM {
|
||||
return ExpressTransactionStateUM.OnrampUM(
|
||||
info = ExpressTransactionStateInfoUM(
|
||||
title = resourceReference(
|
||||
id = R.string.express_status_buying,
|
||||
wrappedList(currency.name),
|
||||
),
|
||||
status = convertStatuses(value.status, value.externalTxUrl),
|
||||
notification = getNotification(value.status, value.externalTxUrl, value.providerName),
|
||||
txId = value.txId,
|
||||
txExternalId = value.externalTxId,
|
||||
txExternalUrl = value.externalTxUrl,
|
||||
timestamp = value.timestamp,
|
||||
timestampFormatted = resourceReference(
|
||||
R.string.send_date_format,
|
||||
wrappedList(
|
||||
value.timestamp.toDateFormatWithTodayYesterday(),
|
||||
value.timestamp.toTimeFormat(),
|
||||
),
|
||||
),
|
||||
toAmount = stringReference(
|
||||
value.toAmount.format { crypto(currency) },
|
||||
),
|
||||
toFiatAmount = stringReference(
|
||||
status.fiatRate?.multiply(value.toAmount).format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
},
|
||||
),
|
||||
toAmountSymbol = currency.symbol,
|
||||
toCurrencyIcon = iconStateConverter.convert(currency),
|
||||
fromAmount = stringReference(
|
||||
value.fromAmount.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = value.fromCurrency.name,
|
||||
fiatCurrencySymbol = value.fromCurrency.code,
|
||||
)
|
||||
},
|
||||
),
|
||||
fromFiatAmount = null,
|
||||
fromAmountSymbol = value.fromCurrency.code,
|
||||
fromCurrencyIcon = CurrencyIconState.FiatIcon(
|
||||
url = value.fromCurrency.image,
|
||||
fallbackResId = R.drawable.ic_currency_24,
|
||||
),
|
||||
iconState = getIconState(value.status),
|
||||
onGoToProviderClick = {
|
||||
analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider)
|
||||
clickIntents.onGoToProviderClick(it)
|
||||
},
|
||||
onClick = {
|
||||
val analyticEvent = TokenOnrampAnalyticsEvent.OnrampStatusOpened(
|
||||
tokenSymbol = currency.symbol,
|
||||
provider = value.providerName,
|
||||
fiatCurrency = value.fromCurrency.code,
|
||||
)
|
||||
analyticsEventHandler.send(analyticEvent)
|
||||
clickIntents.onExpressTransactionClick(value.txId)
|
||||
},
|
||||
),
|
||||
providerName = value.providerName,
|
||||
providerImageUrl = value.providerImageUrl,
|
||||
providerType = value.providerType,
|
||||
activeStatus = value.status,
|
||||
fromCurrencyCode = value.fromCurrency.code,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNotification(
|
||||
status: OnrampStatus.Status,
|
||||
externalTxUrl: String?,
|
||||
providerName: String,
|
||||
): NotificationUM? {
|
||||
if (externalTxUrl == null) return null
|
||||
return when (status) {
|
||||
OnrampStatus.Status.Verifying -> {
|
||||
analyticsEventHandler.send(
|
||||
TokenOnrampAnalyticsEvent.NoticeKYC(currency.symbol, providerName),
|
||||
)
|
||||
ExpressNotificationsUM.NeedVerification {
|
||||
clickIntents.onGoToProviderClick(externalTxUrl)
|
||||
}
|
||||
}
|
||||
OnrampStatus.Status.Failed -> {
|
||||
ExpressNotificationsUM.FailedByProvider {
|
||||
clickIntents.onGoToProviderClick(externalTxUrl)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIconState(status: OnrampStatus.Status): ExpressTransactionStateIconUM {
|
||||
return when (status) {
|
||||
OnrampStatus.Status.Verifying -> ExpressTransactionStateIconUM.Warning
|
||||
OnrampStatus.Status.Failed -> ExpressTransactionStateIconUM.Error
|
||||
else -> ExpressTransactionStateIconUM.None
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertStatuses(status: OnrampStatus.Status, externalTxUrl: String?): ExpressStatusUM {
|
||||
val statuses = with(status) {
|
||||
persistentListOf(
|
||||
getAwaitingDepositItem(),
|
||||
getPaymentProcessingItem(),
|
||||
getBuyingItem(),
|
||||
getSendingItem(),
|
||||
)
|
||||
}
|
||||
|
||||
return ExpressStatusUM(
|
||||
title = resourceReference(R.string.common_transaction_status),
|
||||
link = getStatusLink(status, externalTxUrl),
|
||||
statuses = statuses,
|
||||
)
|
||||
}
|
||||
|
||||
private fun OnrampStatus.Status.getAwaitingDepositItem() = ExpressStatusItemUM(
|
||||
text = when {
|
||||
order < OnrampStatus.Status.WaitingForPayment.order -> {
|
||||
resourceReference(R.string.express_exchange_status_receiving)
|
||||
}
|
||||
this == OnrampStatus.Status.WaitingForPayment -> {
|
||||
resourceReference(R.string.express_exchange_status_receiving_active)
|
||||
}
|
||||
else -> {
|
||||
resourceReference(R.string.express_exchange_status_received)
|
||||
}
|
||||
},
|
||||
state = getStatusState(OnrampStatus.Status.WaitingForPayment),
|
||||
)
|
||||
|
||||
private fun OnrampStatus.Status.getPaymentProcessingItem() = ExpressStatusItemUM(
|
||||
text = when {
|
||||
order < OnrampStatus.Status.PaymentProcessing.order -> {
|
||||
resourceReference(R.string.express_exchange_status_confirming)
|
||||
}
|
||||
this == OnrampStatus.Status.PaymentProcessing -> {
|
||||
resourceReference(R.string.express_exchange_status_confirming_active)
|
||||
}
|
||||
this == OnrampStatus.Status.Verifying -> {
|
||||
resourceReference(R.string.express_exchange_status_verifying)
|
||||
}
|
||||
this == OnrampStatus.Status.Failed -> {
|
||||
resourceReference(R.string.express_exchange_status_failed)
|
||||
}
|
||||
else -> resourceReference(R.string.express_exchange_status_confirmed)
|
||||
},
|
||||
state = when {
|
||||
order < OnrampStatus.Status.PaymentProcessing.order -> {
|
||||
ExpressStatusItemState.Default
|
||||
}
|
||||
this == OnrampStatus.Status.PaymentProcessing -> {
|
||||
ExpressStatusItemState.Active
|
||||
}
|
||||
this == OnrampStatus.Status.Verifying -> {
|
||||
ExpressStatusItemState.Warning
|
||||
}
|
||||
this == OnrampStatus.Status.Failed -> {
|
||||
ExpressStatusItemState.Error
|
||||
}
|
||||
else -> {
|
||||
ExpressStatusItemState.Done
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
private fun OnrampStatus.Status.getBuyingItem() = ExpressStatusItemUM(
|
||||
text = when {
|
||||
order < OnrampStatus.Status.Paid.order -> {
|
||||
resourceReference(R.string.express_status_buying, wrappedList(currency.name))
|
||||
}
|
||||
this == OnrampStatus.Status.Paid -> {
|
||||
resourceReference(
|
||||
R.string.express_status_buying_active,
|
||||
wrappedList(currency.name),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
resourceReference(R.string.express_status_bought, wrappedList(currency.name))
|
||||
}
|
||||
},
|
||||
state = getStatusState(OnrampStatus.Status.Paid),
|
||||
)
|
||||
|
||||
private fun OnrampStatus.Status.getSendingItem() = ExpressStatusItemUM(
|
||||
text = when {
|
||||
order < OnrampStatus.Status.Sending.order -> {
|
||||
resourceReference(
|
||||
R.string.express_exchange_status_sending,
|
||||
wrappedList(currency.name),
|
||||
)
|
||||
}
|
||||
this == OnrampStatus.Status.Sending -> {
|
||||
resourceReference(
|
||||
R.string.express_exchange_status_sending_active,
|
||||
wrappedList(currency.name),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
resourceReference(
|
||||
R.string.express_exchange_status_sent,
|
||||
wrappedList(currency.name),
|
||||
)
|
||||
}
|
||||
},
|
||||
state = getStatusState(OnrampStatus.Status.Sending),
|
||||
)
|
||||
|
||||
private fun getStatusLink(status: OnrampStatus.Status, externalTxUrl: String?): ExpressLinkUM {
|
||||
if (externalTxUrl == null) return ExpressLinkUM.Empty
|
||||
return when (status) {
|
||||
OnrampStatus.Status.Verifying,
|
||||
OnrampStatus.Status.Failed,
|
||||
-> {
|
||||
ExpressLinkUM.Content(
|
||||
icon = R.drawable.ic_arrow_top_right_24,
|
||||
text = resourceReference(R.string.common_go_to_provider),
|
||||
onClick = {
|
||||
clickIntents.onGoToProviderClick(externalTxUrl)
|
||||
},
|
||||
)
|
||||
}
|
||||
else -> ExpressLinkUM.Empty
|
||||
}
|
||||
}
|
||||
|
||||
private fun OnrampStatus.Status.getStatusState(targetState: OnrampStatus.Status) = when {
|
||||
order < targetState.order -> ExpressStatusItemState.Default
|
||||
this == targetState -> ExpressStatusItemState.Active
|
||||
else -> ExpressStatusItemState.Done
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,8 @@ internal class WalletLoadingStateFactory(
|
|||
value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick),
|
||||
),
|
||||
),
|
||||
expressTxsToDisplay = persistentListOf(),
|
||||
expressTxs = persistentListOf(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.onramp.GetOnrampTransactionsUseCase
|
||||
import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetExpressStatusesTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class SingleWalletExpressStatusesSubscriber(
|
||||
private val userWallet: UserWallet,
|
||||
private val stateHolder: WalletStateController,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
|
||||
private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase,
|
||||
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
|
||||
) : WalletSubscriber() {
|
||||
|
||||
override fun create(
|
||||
coroutineScope: CoroutineScope,
|
||||
): Flow<Pair<Either<CurrencyStatusError, CryptoCurrencyStatus>, AppCurrency>> {
|
||||
return combine(
|
||||
flow = getPrimaryCurrencyStatusUpdatesUseCase(userWalletId = userWallet.walletId)
|
||||
.conflate()
|
||||
.distinctUntilChanged(),
|
||||
flow2 = getSelectedAppCurrencyUseCase()
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.map { maybeAppCurrency -> maybeAppCurrency.getOrElse { AppCurrency.Default } },
|
||||
transform = { maybeCurrencyStatus, appCurrency -> maybeCurrencyStatus to appCurrency },
|
||||
).onEach { maybeCurrencyStatusAndAppCurrency ->
|
||||
val status = maybeCurrencyStatusAndAppCurrency.first.getOrElse {
|
||||
Timber.e("Unable to get primary currency status: $it")
|
||||
return@onEach
|
||||
}
|
||||
|
||||
getOnrampTransactionsUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrencyId = status.currency.id,
|
||||
).onEach { maybeTransaction ->
|
||||
maybeTransaction.fold(
|
||||
ifRight = { onrampTxs ->
|
||||
onrampTxs.clearHiddenTerminal()
|
||||
stateHolder.update(
|
||||
SetExpressStatusesTransformer(
|
||||
userWalletId = userWallet.walletId,
|
||||
onrampTxs = onrampTxs,
|
||||
clickIntents = clickIntents,
|
||||
cryptoCurrencyStatus = status,
|
||||
appCurrency = maybeCurrencyStatusAndAppCurrency.second,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
),
|
||||
)
|
||||
},
|
||||
ifLeft = {
|
||||
stateHolder.update(
|
||||
SetExpressStatusesTransformer(
|
||||
userWalletId = userWallet.walletId,
|
||||
onrampTxs = listOf(),
|
||||
clickIntents = clickIntents,
|
||||
cryptoCurrencyStatus = status,
|
||||
appCurrency = maybeCurrencyStatusAndAppCurrency.second,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun List<OnrampTransaction>.clearHiddenTerminal() {
|
||||
this.filter { it.status.isHidden && it.status.isTerminal }
|
||||
.forEach { onrampRemoveTransactionUseCase(externalTxId = it.externalTxId) }
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,9 @@ import androidx.compose.ui.unit.IntOffset
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheet
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||
import com.tangem.common.ui.expressStatus.expressTransactionsItems
|
||||
import com.tangem.core.ui.components.atoms.Hand
|
||||
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
|
|
@ -216,6 +219,12 @@ private fun WalletContent(
|
|||
walletState.marketPriceBlockState?.let { marketPriceBlockState ->
|
||||
marketPriceBlock(state = marketPriceBlockState, modifier = itemModifier)
|
||||
}
|
||||
if (walletState is WalletState.SingleCurrency.Content) {
|
||||
expressTransactionsItems(
|
||||
expressTxs = walletState.expressTxsToDisplay,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
(selectedWallet as? WalletState.Visa.Content)?.let {
|
||||
|
|
@ -716,6 +725,7 @@ private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
|
|||
is BalancesAndLimitsBottomSheetConfig -> BalancesAndLimitsBottomSheet(config = bottomSheetConfig)
|
||||
is VisaTxDetailsBottomSheetConfig -> VisaTxDetailsBottomSheet(config = bottomSheetConfig)
|
||||
is PushNotificationsBottomSheetConfig -> PushNotificationsBottomSheet(config = bottomSheetConfig)
|
||||
is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(config = bottomSheetConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
|||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWalletAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||
|
|
@ -33,9 +34,7 @@ import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
|||
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import com.tangem.utils.coroutines.*
|
||||
import com.tangem.utils.extensions.indexOfFirstOrNull
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -70,6 +69,7 @@ internal class WalletViewModel @Inject constructor(
|
|||
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase,
|
||||
private val walletImageResolver: WalletImageResolver,
|
||||
private val tokenListStore: MultiWalletTokenListStore,
|
||||
private val onrampStatusFactory: OnrampStatusFactory,
|
||||
private val walletFeatureToggles: WalletFeatureToggles,
|
||||
analyticsEventsHandler: AnalyticsEventHandler,
|
||||
) : ViewModel() {
|
||||
|
|
@ -81,6 +81,8 @@ internal class WalletViewModel @Inject constructor(
|
|||
private val refreshWalletJobHolder = JobHolder()
|
||||
private var needToRefreshWallet = false
|
||||
|
||||
private var expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>()
|
||||
|
||||
init {
|
||||
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpened)
|
||||
|
||||
|
|
@ -93,6 +95,7 @@ internal class WalletViewModel @Inject constructor(
|
|||
subscribeOnSelectedWalletFlow()
|
||||
subscribeToScreenBackgroundState()
|
||||
subscribeOnPushNotificationsPermission()
|
||||
subscribeOnExpressTransactionsUpdates()
|
||||
}
|
||||
|
||||
private fun maybeMigrateNames() {
|
||||
|
|
@ -239,6 +242,24 @@ internal class WalletViewModel @Inject constructor(
|
|||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnExpressTransactionsUpdates() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
expressTxStatusTaskScheduler.cancelTask()
|
||||
expressTxStatusTaskScheduler.scheduleTask(
|
||||
viewModelScope,
|
||||
PeriodicTask(
|
||||
isDelayFirst = false,
|
||||
delay = EXPRESS_STATUS_UPDATE_DELAY,
|
||||
task = {
|
||||
runCatching { onrampStatusFactory.updateOnrmapTransactionStatuses() }
|
||||
},
|
||||
onSuccess = { /* no-op */ },
|
||||
onError = { /* no-op */ },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun needToRefreshTimer() {
|
||||
viewModelScope.launch {
|
||||
delay(REFRESH_WALLET_BACKGROUND_TIMER_MILLIS)
|
||||
|
|
@ -430,5 +451,6 @@ internal class WalletViewModel @Inject constructor(
|
|||
|
||||
private companion object {
|
||||
const val REFRESH_WALLET_BACKGROUND_TIMER_MILLIS = 10000L
|
||||
const val EXPRESS_STATUS_UPDATE_DELAY = 10000L
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.settings.ShouldShowMarketsTooltipUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||
|
|
@ -11,10 +12,14 @@ import com.tangem.domain.tokens.model.TokenActionsState
|
|||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.ActionsBottomSheetConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomSheetConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCurrencyActionsConverter
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
|
|
@ -43,6 +48,12 @@ internal interface WalletContentClickIntents {
|
|||
fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onTransactionClick(txHash: String)
|
||||
|
||||
fun onDissmissBottomSheet()
|
||||
|
||||
fun onGoToProviderClick(externalTxId: String)
|
||||
|
||||
fun onExpressTransactionClick(txId: String)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -51,6 +62,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
private val stateHolder: WalletStateController,
|
||||
private val currencyActionsClickIntents: WalletCurrencyActionsClickIntentsImplementor,
|
||||
private val walletWarningsClickIntents: WalletWarningsClickIntentsImplementor,
|
||||
private val onrampStatusFactory: OnrampStatusFactory,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
|
||||
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
|
||||
|
|
@ -164,4 +176,39 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGoToProviderClick(externalTxUrl: String) {
|
||||
router.openUrl(externalTxUrl)
|
||||
}
|
||||
|
||||
override fun onDissmissBottomSheet() {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
if (stateHolder.getSelectedWallet().bottomSheetConfig?.content is ExpressStatusBottomSheetConfig) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
onrampStatusFactory.removeTransactionOnBottomSheetClosed()
|
||||
}
|
||||
}
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId))
|
||||
}
|
||||
|
||||
override fun onExpressTransactionClick(txId: String) {
|
||||
viewModelScope.launch {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
val singleWalletState = stateHolder.getSelectedWallet() as? WalletState.SingleCurrency.Content
|
||||
?: return@launch
|
||||
|
||||
val expressTransaction = singleWalletState.expressTxsToDisplay.firstOrNull { it.info.txId == txId }
|
||||
?: return@launch
|
||||
|
||||
stateHolder.update(
|
||||
OpenBottomSheetTransformer(
|
||||
userWalletId = userWalletId,
|
||||
content = ExpressStatusBottomSheetConfig(
|
||||
value = expressTransaction,
|
||||
),
|
||||
onDismissBottomSheet = ::onDissmissBottomSheet,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue