Updated on 2026-08-14
This commit is contained in:
parent
7bb835032c
commit
68f800a495
47 changed files with 408 additions and 436 deletions
|
|
@ -1,7 +1,7 @@
|
|||
==========================================
|
||||
Detekt Baseline Updater & Issue Counter
|
||||
==========================================
|
||||
Date: 2025-11-26 17:43:14
|
||||
Date: 2025-11-27 14:13:20
|
||||
|
||||
Step 1: Running detekt to check for new issues...
|
||||
|
||||
|
|
@ -17,13 +17,13 @@ Counting issues in baseline files...
|
|||
==========================================
|
||||
|
||||
Summary:
|
||||
Total Issues: 1675
|
||||
Total Issues: 1593
|
||||
Modules with Issues: 68
|
||||
Average Issues per Module: 24
|
||||
Average Issues per Module: 23
|
||||
|
||||
Progress:
|
||||
Fixed: 127 out of 1802 (7%)
|
||||
Remaining: 1675
|
||||
Fixed: 209 out of 1802 (11%)
|
||||
Remaining: 1593
|
||||
|
||||
==========================================
|
||||
All Modules with Issues (sorted by count)
|
||||
|
|
@ -34,7 +34,6 @@ Module Issues
|
|||
features/wallet/impl 169
|
||||
features/markets/impl 155
|
||||
features/onboarding-v2/impl 131
|
||||
features/onramp/impl 89
|
||||
features/send-v2/impl 80
|
||||
features/swap/impl 73
|
||||
features/hot-wallet/impl 57
|
||||
|
|
@ -74,6 +73,7 @@ features/welcome/impl 8
|
|||
features/home/impl 8
|
||||
domain/transaction 8
|
||||
libs/tangem-sdk-api 7
|
||||
features/onramp/impl 7
|
||||
data/txhistory 7
|
||||
data/tokens 7
|
||||
data/account 7
|
||||
|
|
|
|||
|
|
@ -2,41 +2,6 @@
|
|||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>BooleanPropertyNaming:ConfirmResidencyComponent.kt$ConfirmResidencyComponent.Params$val launchSepa: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:DefaultHotCryptoComponent.kt$DefaultHotCryptoComponent$val scrollableContent = when (stack.active.configuration) { OnrampAddTokenRoute.PortfolioSelector -> false OnrampAddTokenRoute.AddToken, OnrampAddTokenRoute.Empty, -> true }</ID>
|
||||
<ID>BooleanPropertyNaming:OnrampAddTokenComponent.kt$OnrampAddTokenComponent.AddHotCryptoData$val availableMorePortfolio: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:OnrampMainComponent.kt$OnrampMainComponent.Params$val launchSepa: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:OnrampMainComponentUM.kt$BuyButtonConfig$val enabled: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:ProviderListPaymentMethodUM.kt$ProviderListPaymentMethodUM$val enabled: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:SelectProviderModel.kt$SelectProviderModel$val allErrorProviders = providers.all { it is OnrampProviderWithQuote.Unavailable.NotSupportedPaymentMethod }</ID>
|
||||
<ID>MaxChainedCallsOnSameLine:OnrampMainComponentModel.kt$OnrampMainComponentModel$content.amountBlockState.amountFieldModel.fiatAmount.value.isNullOrZero()</ID>
|
||||
<ID>MaxChainedCallsOnSameLine:OnrampV2MainComponentModel.kt$OnrampV2MainComponentModel$content.amountBlockState.amountFieldModel.fiatAmount.value.isNullOrZero()</ID>
|
||||
<ID>MultilineLambdaItParameter:AvailableSwapPairsModel.kt$AvailableSwapPairsModel${ handleErrorState( cause = it, networkInfo = params.selectedStatus.value?.toLeastTokenInfo(), currencies = currencies, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:AvailableSwapPairsModel.kt$AvailableSwapPairsModel${ handleErrorStateV2( cause = it, networkInfo = params.selectedStatus.value?.toLeastTokenInfo(), accountList = accountList, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:AvailableSwapPairsModel.kt$AvailableSwapPairsModel${ it.currency.name.contains(other = query, ignoreCase = true) || it.currency.symbol.contains(other = query, ignoreCase = true) }</ID>
|
||||
<ID>MultilineLambdaItParameter:AvailableSwapPairsModel.kt$AvailableSwapPairsModel${ it.toMutableMap().apply { put(networkInfo, state) } }</ID>
|
||||
<ID>MultilineLambdaItParameter:DefaultOnrampComponent.kt$DefaultOnrampComponent${ navigation.push( OnrampChild.RedirectPage( quote = it, cryptoCurrency = params.cryptoCurrency, ), ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:HotCryptoModel.kt$HotCryptoModel${ Timber.e(it) closeNavigationFlow() }</ID>
|
||||
<ID>MultilineLambdaItParameter:HotCryptoModel.kt$HotCryptoModel${ bottomSheetNavigation.dismiss() params.onTokenClick(it) }</ID>
|
||||
<ID>MultilineLambdaItParameter:HotCryptoPortfolioDataLoader.kt$HotCryptoPortfolioDataLoader${ val account: AccountStatus.CryptoPortfolio = when (it) { is AccountStatus.CryptoPortfolio -> it } val addedHotCrypto = mapOfAddedCurrencies[account.account] ?: listOf() HotCryptoPortfolioData.Account( account = account, addedHotCrypto = addedHotCrypto, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampAddToPortfolioModel.kt$OnrampAddToPortfolioModel${ Timber.e("Failed to derive public keys: $it") changeAddButtonProgressStatus(isProgress = false) }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampAddTokenModel.kt$OnrampAddTokenModel${ processError(error = it) uiState.value = um.toggleProgress(false) return@launch }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampMainComponentModel.kt$OnrampMainComponentModel${ (it as? OnrampMainComponentUM.Content)?.copy( errorNotification = null, providerBlockState = OnrampProviderBlockUM.Loading, amountBlockState = it.amountBlockState.copy(secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading), ) ?: it }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampMainComponentModel.kt$OnrampMainComponentModel${ if (it is OnrampMainComponentUM.InitialLoading) { stateFactory.getReadyState(country.defaultCurrency) } else { amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency) } }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampMainComponentModel.kt$OnrampMainComponentModel${ it.provider.id == providerState?.providerId && it.paymentMethod.id == providerState.paymentMethod.id }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampOffersContent.kt${ SpacerH(12.dp) SecondaryButton( modifier = Modifier.fillMaxWidth(), text = it.title.resolveReference(), onClick = it.onClick, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampOffersContent.kt${ Text( modifier = Modifier .background( color = TangemTheme.colors.text.warning.copy(alpha = 0.1f), shape = RoundedCornerShape(4.dp), ) .padding(horizontal = 4.dp), text = it.resolveReference(), style = TangemTheme.typography.caption1, color = TangemTheme.colors.text.warning, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampRedirectModel.kt$OnrampRedirectModel${ latestOnrampTransaction = it // Workaround to open Unlimit provider in external browser instead of chrome custom tabs if (params.onrampProviderWithQuote.provider.id.equals(UNLIMIT_PROVIDER_ID, ignoreCase = true)) { urlOpener.openUrlExternalBrowser(it.redirectUrl) } else { urlOpener.openUrl(it.redirectUrl) } }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampTokenItemStateConverterFactory.kt$OnrampTokenItemStateConverterFactory${ TokenItemState.TitleState.Content( text = stringReference(value = it.currency.name), isAvailable = false, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampTokenItemStateConverterFactory.kt$OnrampTokenItemStateConverterFactory${ createSubtitleState( status = it, isAvailable = false, text = unavailableErrorText, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampTokenItemStateConverterFactory.kt$OnrampTokenItemStateConverterFactory${ createSubtitleState( status = it, isAvailable = true, text = stringReference(value = it.currency.symbol), ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampTokenItemStateConverterFactory.kt$OnrampTokenItemStateConverterFactory${ createSubtitleState( status = it, text = stringReference(value = it.currency.symbol), isAvailable = false, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampTokenListModel.kt$OnrampTokenListModel${ if (hasRestrictionForSell || isInsufficientBalanceForSell) { mapOf(false to it) } else { it.filterByAvailability() } }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampTokenListModel.kt$OnrampTokenListModel${ it.currency.name.contains(other = query, ignoreCase = true) || it.currency.symbol.contains(other = query, ignoreCase = true) }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampV2MainComponentModel.kt$OnrampV2MainComponentModel${ (it as? OnrampV2MainComponentUM.Content)?.copy( errorNotification = null, offersBlockState = OnrampOffersBlockUM.Loading, amountBlockState = it.amountBlockState.copy(secondaryFieldModel = OnrampSecondaryFieldErrorUM.Empty), ) ?: it }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampV2MainComponentModel.kt$OnrampV2MainComponentModel${ when (it) { is OnrampV2MainComponentUM.Content -> { amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency) } is OnrampV2MainComponentUM.InitialLoading -> { stateFactory.getReadyState(country.defaultCurrency) } } }</ID>
|
||||
<ID>MultilineLambdaItParameter:SelectProviderModel.kt$SelectProviderModel${ when (it) { is OnrampProviderWithQuote.Data -> it.toAmount.value // negative difference to sort both when data and unavailable is present is OnrampProviderWithQuote.Unavailable.AmountError -> { when (val error = it.quoteError.error) { is OnrampError.AmountError.TooSmallError -> it.quoteError.fromAmount.value - error.requiredAmount is OnrampError.AmountError.TooBigError -> error.requiredAmount - it.quoteError.fromAmount.value else -> null } } is OnrampProviderWithQuote.Unavailable.NotSupportedPaymentMethod -> null } }</ID>
|
||||
<ID>NoNameShadowing:OnrampAddTokenUiBuilder.kt$OnrampAddTokenUiBuilder$tokenToAdd</ID>
|
||||
<ID>NonBooleanPropertyPrefixedWithIs:AvailableSwapPairsModel.kt$AvailableSwapPairsModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
|
||||
<ID>NonBooleanPropertyPrefixedWithIs:HotCryptoModel.kt$HotCryptoModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
|
||||
<ID>NonBooleanPropertyPrefixedWithIs:OnrampAddTokenUiBuilder.kt$OnrampAddTokenUiBuilder$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
|
||||
|
|
@ -44,52 +9,5 @@
|
|||
<ID>NonBooleanPropertyPrefixedWithIs:OnrampOperationModel.kt$OnrampOperationModel$private val isDemoCardUseCase: IsDemoCardUseCase</ID>
|
||||
<ID>NonBooleanPropertyPrefixedWithIs:OnrampTokenListModel.kt$OnrampTokenListModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
|
||||
<ID>NonBooleanPropertyPrefixedWithIs:SwapSelectTokensModel.kt$SwapSelectTokensModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase</ID>
|
||||
<ID>NullableBooleanCheck:HotCryptoModel.kt$HotCryptoModel$hotCryptoPortfolioData.wallet.accounts .find { it.account.accountId == accountStatus.accountId } ?.addedHotCrypto ?.none { it.currency.id.rawCurrencyId == hotCrypto.cryptoCurrency.id.rawCurrencyId } ?: false</ID>
|
||||
<ID>NullableToStringCall:SwapSelectTokensController.kt$SwapSelectTokensController$${transformer::class.simpleName}</ID>
|
||||
<ID>NullableToStringCall:TokenListUMController.kt$TokenListUMController$${transformer::class.simpleName}</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:AllOffersModel.kt$AllOffersModel$state</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:CountryListUMController.kt$CountryListUMController$_state</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:CurrencyListController.kt$CurrencyListController$_state</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:HotCryptoModel.kt$HotCryptoModel$_state</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:OnrampAddToPortfolioModel.kt$OnrampAddToPortfolioModel$_state</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:OnrampMainComponentModel.kt$OnrampMainComponentModel$_state</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:OnrampMainComponentModel.kt$OnrampMainComponentModel$userCountry</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:OnrampOperationModel.kt$OnrampOperationModel$_state</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:OnrampSelectCountryModel.kt$OnrampSelectCountryModel$controller</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:OnrampSelectCurrencyModel.kt$OnrampSelectCurrencyModel$controller</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:OnrampSettingsModel.kt$OnrampSettingsModel$_state</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:OnrampV2MainComponentModel.kt$OnrampV2MainComponentModel$_state</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:SelectProviderModel.kt$SelectProviderModel$_state</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:SwapSelectTokensController.kt$SwapSelectTokensController$_state</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:SwapSelectTokensModel.kt$SwapSelectTokensModel$_fromCurrencyStatus</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:TokenListUMController.kt$TokenListUMController$_state</ID>
|
||||
<ID>RedundantSuspendModifier:OnrampSelectCountryModel.kt$OnrampSelectCountryModel$suspend</ID>
|
||||
<ID>ReusedModifierInstance:DefaultOnrampComponent.kt$DefaultOnrampComponent$Content(modifier = modifier)</ID>
|
||||
<ID>SuspendFunSwallowedCancellation:AvailableSwapPairsModel.kt$AvailableSwapPairsModel$runCatching</ID>
|
||||
<ID>SuspendFunSwallowedCancellation:OnrampMainComponentModel.kt$OnrampMainComponentModel$runCatching</ID>
|
||||
<ID>SuspendFunSwallowedCancellation:OnrampSuccessComponentModel.kt$OnrampSuccessComponentModel$runCatching</ID>
|
||||
<ID>SuspendFunSwallowedCancellation:OnrampV2MainComponentModel.kt$OnrampV2MainComponentModel$runCatching</ID>
|
||||
<ID>UnnecessaryApply:OnrampFooterContent.kt$apply { AnimatedVisibility( modifier = Modifier .imePadding() .align(Alignment.BottomCenter), visible = state.offersBlockState is OnrampOffersBlockUM.Empty, enter = slideInVertically( initialOffsetY = { it }, animationSpec = tween(durationMillis = 300), ), exit = slideOutVertically( targetOffsetY = { it }, animationSpec = tween(durationMillis = 300), ), label = "Footer block animation", ) { Column( modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, ) { SpacerH(16.dp) OnrampAmountButtons(state = state.onrampAmountButtonUMState) } } }</ID>
|
||||
<ID>UnnecessaryLet:AllOffersModel.kt$AllOffersModel$let { analyticsEventHandler::send }</ID>
|
||||
<ID>UnnecessaryLet:HotTokenItemStateConverter.kt$HotTokenItemStateConverter$let { onItemClick -> { onItemClick(it, value) } }</ID>
|
||||
<ID>UnnecessaryLet:OnrampButtonComponent.kt$let { state.onLinkClick(privacyPolicyLink) }</ID>
|
||||
<ID>UnnecessaryLet:OnrampButtonComponent.kt$let { state.onLinkClick(termsOfUseLink) }</ID>
|
||||
<ID>UnnecessaryLet:OnrampTokenListModel.kt$OnrampTokenListModel$let(::resourceReference)</ID>
|
||||
<ID>UnnecessaryLet:OnrampTokenListUMExt.kt$let(::add)</ID>
|
||||
<ID>UnnecessaryLet:OnrampV2MainComponentModel.kt$OnrampV2MainComponentModel$let(analyticsEventHandler::send)</ID>
|
||||
<ID>UnnecessaryLet:SetNothingToFoundStateTransformer.kt$SetNothingToFoundStateTransformer$let(::add)</ID>
|
||||
<ID>UnnecessaryLet:SetNothingToFoundStateTransformerV2.kt$SetNothingToFoundStateTransformerV2$let(::add)</ID>
|
||||
<ID>UnsafeCallOnNullableType:DefaultHotCryptoComponent.kt$DefaultHotCryptoComponent$addTokenComponent!!</ID>
|
||||
<ID>UnsafeCallOnNullableType:DefaultHotCryptoComponent.kt$DefaultHotCryptoComponent$model.portfolioFetcher!!</ID>
|
||||
<ID>UnsafeCallOnNullableType:DefaultHotCryptoComponent.kt$DefaultHotCryptoComponent$portfolioSelectorComponent!!</ID>
|
||||
<ID>UseEmptyCounterpart:HotCryptoPortfolioDataLoader.kt$HotCryptoPortfolioDataLoader$listOf()</ID>
|
||||
<ID>UseEmptyCounterpart:SelectProviderPreviewData.kt$SelectProviderPreviewData$listOf()</ID>
|
||||
<ID>UseOrEmpty:AvailableSwapPairsModel.kt$AvailableSwapPairsModel$selectedStatus?.currency?.name?.capitalize() ?: ""</ID>
|
||||
<ID>UseOrEmpty:HotCryptoPortfolioDataLoader.kt$HotCryptoPortfolioDataLoader$getAccountCurrencyStatusUseCase .invokeSync(userWalletId, hotCryptoCurrencies) .getOrNull() ?: emptyMap()</ID>
|
||||
<ID>UseOrEmpty:HotCryptoPortfolioDataLoader.kt$HotCryptoPortfolioDataLoader$mapOfAddedCurrencies[account.account] ?: listOf()</ID>
|
||||
<ID>UselessCallOnNotNull:OnrampAddToPortfolioModel.kt$OnrampAddToPortfolioModel$listOfNotNull(params.cryptoCurrency)</ID>
|
||||
<ID>VarCouldBeVal:AvailableSwapPairsModel.kt$AvailableSwapPairsModel$private var params: AvailableSwapPairsComponent.Params = paramsContainer.require()</ID>
|
||||
<ID>VarCouldBeVal:OnrampSuccessComponentModel.kt$OnrampSuccessComponentModel$private var expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>()</ID>
|
||||
<ID>VarCouldBeVal:SwapSelectTokensModel.kt$SwapSelectTokensModel$private var account: Account.CryptoPortfolio? = null</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
|
|||
|
|
@ -9,13 +9,17 @@ import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
|||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.features.onramp.alloffers.AllOffersComponent
|
||||
import com.tangem.features.onramp.alloffers.entity.AllOffersIntents
|
||||
import com.tangem.features.onramp.alloffers.entity.AllOffersPaymentMethodUM
|
||||
import com.tangem.features.onramp.alloffers.entity.AllOffersStateFactory
|
||||
import com.tangem.features.onramp.alloffers.entity.AllOffersStateUM
|
||||
import com.tangem.features.onramp.mainv2.entity.OnrampOfferAdvantagesUM
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
|
@ -27,6 +31,9 @@ internal class AllOffersModel @Inject constructor(
|
|||
paramsContainer: ParamsContainer,
|
||||
) : Model(), AllOffersIntents {
|
||||
|
||||
val state: StateFlow<AllOffersStateUM>
|
||||
field = MutableStateFlow<AllOffersStateUM>(AllOffersStateUM.Loading)
|
||||
|
||||
private var quotesJob: Job? = null
|
||||
|
||||
private val stateFactory: AllOffersStateFactory by lazy(LazyThreadSafetyMode.NONE) {
|
||||
|
|
@ -39,9 +46,6 @@ internal class AllOffersModel @Inject constructor(
|
|||
|
||||
private val params: AllOffersComponent.Params = paramsContainer.require()
|
||||
|
||||
private val _state: MutableStateFlow<AllOffersStateUM> = MutableStateFlow(AllOffersStateUM.Loading)
|
||||
val state: StateFlow<AllOffersStateUM> = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
subscribeOnAllOffers()
|
||||
analyticsEventHandler.send(OnrampAnalyticsEvent.PaymentMethodsScreenOpened)
|
||||
|
|
@ -53,12 +57,20 @@ internal class AllOffersModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onPaymentMethodClicked(paymentMethodId: String) {
|
||||
val contentState = state.value as? AllOffersStateUM.Content ?: return
|
||||
val method = contentState.methods.firstOrNull { it.methodConfig.method.id == paymentMethodId } ?: return
|
||||
analyticsEventHandler.send(
|
||||
event = OnrampAnalyticsEvent.OnPaymentMethodChosen(paymentMethod = method.methodConfig.method.name),
|
||||
)
|
||||
_state.update { contentState.copy(currentMethod = method) }
|
||||
var method: AllOffersPaymentMethodUM? = null
|
||||
|
||||
state.update { contentState ->
|
||||
if (contentState !is AllOffersStateUM.Content) return
|
||||
method = contentState.methods.firstOrNull { it.methodConfig.method.id == paymentMethodId } ?: return
|
||||
|
||||
contentState.copy(currentMethod = method)
|
||||
}
|
||||
|
||||
if (method != null) {
|
||||
analyticsEventHandler.send(
|
||||
event = OnrampAnalyticsEvent.OnPaymentMethodChosen(paymentMethod = method.methodConfig.method.name),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBuyClick(quote: OnrampProviderWithQuote.Data, onrampOfferAdvantagesUM: OnrampOfferAdvantagesUM) {
|
||||
|
|
@ -69,17 +81,23 @@ internal class AllOffersModel @Inject constructor(
|
|||
tokenSymbol = params.cryptoCurrency.symbol,
|
||||
),
|
||||
)
|
||||
onrampOfferAdvantagesUM.toAnalyticsEvent(
|
||||
|
||||
val event = onrampOfferAdvantagesUM.toAnalyticsEvent(
|
||||
cryptoCurrencySymbol = params.cryptoCurrency.symbol,
|
||||
providerName = quote.provider.info.name,
|
||||
paymentMethodName = quote.paymentMethod.name,
|
||||
)?.let { analyticsEventHandler::send }
|
||||
)
|
||||
|
||||
if (event != null) {
|
||||
analyticsEventHandler.send(event)
|
||||
}
|
||||
|
||||
dismiss()
|
||||
params.openRedirectPage(quote)
|
||||
}
|
||||
|
||||
override fun onBackClicked() {
|
||||
_state.update { stateFactory.getPaymentsState() }
|
||||
state.update { stateFactory.getPaymentsState() }
|
||||
}
|
||||
|
||||
override fun onRefresh() {
|
||||
|
|
@ -96,7 +114,7 @@ internal class AllOffersModel @Inject constructor(
|
|||
maybeOffers.fold(
|
||||
ifLeft = ::handleOnrampError,
|
||||
ifRight = { offersGroup ->
|
||||
_state.update {
|
||||
state.update {
|
||||
stateFactory.getLoadedPaymentsState(
|
||||
methodGroups = offersGroup,
|
||||
currencyCode = params.amountCurrencyCode,
|
||||
|
|
@ -110,6 +128,6 @@ internal class AllOffersModel @Inject constructor(
|
|||
|
||||
private fun handleOnrampError(onrampError: OnrampError) {
|
||||
Timber.e(onrampError.toString())
|
||||
_state.update { stateFactory.getOnrampErrorState(onrampError) }
|
||||
state.update { stateFactory.getOnrampErrorState(onrampError) }
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@ package com.tangem.features.onramp.confirmresidency
|
|||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
|
||||
internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent {
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent {
|
|||
val userWalletId: UserWalletId,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val country: OnrampCountry,
|
||||
val launchSepa: Boolean,
|
||||
val isLaunchSepa: Boolean,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ internal class ConfirmResidencyModel @Inject constructor(
|
|||
analyticsEventHandler.send(OnrampAnalyticsEvent.OnResidenceConfirm(country.name))
|
||||
modelScope.launch {
|
||||
saveDefaultCountryUseCase.invoke(country)
|
||||
if (params.launchSepa) {
|
||||
if (params.isLaunchSepa) {
|
||||
onrampSaveDefaultCurrencyUseCase.invoke(EUR_CURRENCY)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
|
|
@ -57,7 +60,7 @@ internal class DefaultHotCryptoComponent @AssistedInject constructor(
|
|||
portfolioSelectorComponentFactory.create(
|
||||
context = child("portfolioSelectorComponent"),
|
||||
params = PortfolioSelectorComponent.Params(
|
||||
portfolioFetcher = model.portfolioFetcher!!,
|
||||
portfolioFetcher = requireNotNull(model.portfolioFetcher),
|
||||
controller = model.portfolioSelectorController,
|
||||
),
|
||||
)
|
||||
|
|
@ -146,13 +149,13 @@ internal class DefaultHotCryptoComponent @AssistedInject constructor(
|
|||
end = 16.dp,
|
||||
bottom = 16.dp,
|
||||
)
|
||||
val scrollableContent = when (stack.active.configuration) {
|
||||
val isScrollableContent = when (stack.active.configuration) {
|
||||
OnrampAddTokenRoute.PortfolioSelector -> false
|
||||
OnrampAddTokenRoute.AddToken,
|
||||
OnrampAddTokenRoute.Empty,
|
||||
-> true
|
||||
}
|
||||
if (scrollableContent) {
|
||||
if (isScrollableContent) {
|
||||
Column(
|
||||
modifier = paddingModifier.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
|
|
@ -218,8 +221,8 @@ internal class DefaultHotCryptoComponent @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun contentChild(config: OnrampAddTokenRoute): ComposableContentComponent = when (config) {
|
||||
OnrampAddTokenRoute.AddToken -> addTokenComponent!!
|
||||
OnrampAddTokenRoute.PortfolioSelector -> portfolioSelectorComponent!!
|
||||
OnrampAddTokenRoute.AddToken -> requireNotNull(addTokenComponent)
|
||||
OnrampAddTokenRoute.PortfolioSelector -> requireNotNull(portfolioSelectorComponent)
|
||||
OnrampAddTokenRoute.Empty -> ComposableContentComponent.EMPTY
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ internal class HotTokenItemStateConverter(
|
|||
subtitleState = value.quoteStatus.getCryptoPriceState(appCurrency),
|
||||
fiatAmountState = null,
|
||||
subtitle2State = null,
|
||||
onItemClick = onItemClick.let { onItemClick -> { onItemClick(it, value) } },
|
||||
onItemClick = { onItemClick(it, value) },
|
||||
onItemLongClick = null,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@ internal class HotCryptoModel @Inject constructor(
|
|||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||
)
|
||||
|
||||
val state: StateFlow<HotCryptoUM> get() = _state
|
||||
private val _state = MutableStateFlow(value = HotCryptoUM(items = persistentListOf()))
|
||||
val state: StateFlow<HotCryptoUM>
|
||||
field = MutableStateFlow(value = HotCryptoUM(items = persistentListOf()))
|
||||
|
||||
private val params: HotCryptoComponent.Params = paramsContainer.require()
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ internal class HotCryptoModel @Inject constructor(
|
|||
.map(TokensListItemUM::Token)
|
||||
},
|
||||
)
|
||||
.onEach { items -> _state.update { HotCryptoUM(items = it.buildItems(items)) } }
|
||||
.onEach { items -> state.update { HotCryptoUM(items = it.buildItems(items)) } }
|
||||
.flowOn(dispatchers.default)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
|
@ -131,7 +131,7 @@ internal class HotCryptoModel @Inject constructor(
|
|||
.map(TokensListItemUM::Token)
|
||||
}
|
||||
.onEach { items ->
|
||||
_state.update { HotCryptoUM(items = it.buildItems(items)) }
|
||||
state.update { HotCryptoUM(items = it.buildItems(items)) }
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
|
@ -176,7 +176,7 @@ internal class HotCryptoModel @Inject constructor(
|
|||
cryptoCurrency = currency.cryptoCurrency,
|
||||
userWallet = userWallet,
|
||||
account = account,
|
||||
availableMorePortfolio = false,
|
||||
isMorePortfolioAvailable = false,
|
||||
)
|
||||
hotCryptoToAddDataFlow.emit(tokenToAdd)
|
||||
bottomSheetNavigationV2.replaceAll(OnrampAddTokenRoute.AddToken)
|
||||
|
|
@ -192,7 +192,7 @@ internal class HotCryptoModel @Inject constructor(
|
|||
cryptoCurrency = currency.cryptoCurrency,
|
||||
userWallet = userWallet,
|
||||
account = selectedAccount,
|
||||
availableMorePortfolio = true,
|
||||
isMorePortfolioAvailable = true,
|
||||
)
|
||||
}
|
||||
.shareIn(this, started = SharingStarted.Eagerly)
|
||||
|
|
@ -218,8 +218,8 @@ internal class HotCryptoModel @Inject constructor(
|
|||
closeNavigationFlow()
|
||||
channel.close()
|
||||
}
|
||||
.catch {
|
||||
Timber.e(it)
|
||||
.catch { throwable ->
|
||||
Timber.e(throwable)
|
||||
closeNavigationFlow()
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
|
|
@ -232,9 +232,9 @@ internal class HotCryptoModel @Inject constructor(
|
|||
userWalletId = params.userWalletId,
|
||||
cryptoCurrencyId = id,
|
||||
)
|
||||
.onRight {
|
||||
.onRight { status ->
|
||||
bottomSheetNavigation.dismiss()
|
||||
params.onTokenClick(it)
|
||||
params.onTokenClick(status)
|
||||
}
|
||||
.onLeft { Timber.d("Unable to get CryptoCurrencyStatus[$id]: $it") }
|
||||
}
|
||||
|
|
@ -246,8 +246,8 @@ internal class HotCryptoModel @Inject constructor(
|
|||
val isNotAddedHotCrypto = hotCryptoPortfolioData.wallet.accounts
|
||||
.find { it.account.accountId == accountStatus.accountId }
|
||||
?.addedHotCrypto
|
||||
?.none { it.currency.id.rawCurrencyId == hotCrypto.cryptoCurrency.id.rawCurrencyId }
|
||||
?: false
|
||||
?.none { it.currency.id.rawCurrencyId == hotCrypto.cryptoCurrency.id.rawCurrencyId } == true
|
||||
|
||||
return@isEnabled isNotAddedHotCrypto
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,12 +41,12 @@ internal class HotCryptoPortfolioDataLoader @Inject constructor(
|
|||
val mapOfAddedCurrencies: AccountCryptoCurrencyStatuses = getAccountCurrencyStatusUseCase
|
||||
.invokeSync(userWalletId, hotCryptoCurrencies)
|
||||
.getOrNull()
|
||||
?: emptyMap()
|
||||
val accountsWithHotCrypto = walletAccounts.accountStatuses.map {
|
||||
val account: AccountStatus.CryptoPortfolio = when (it) {
|
||||
is AccountStatus.CryptoPortfolio -> it
|
||||
.orEmpty()
|
||||
val accountsWithHotCrypto = walletAccounts.accountStatuses.map { accountStatus ->
|
||||
val account: AccountStatus.CryptoPortfolio = when (accountStatus) {
|
||||
is AccountStatus.CryptoPortfolio -> accountStatus
|
||||
}
|
||||
val addedHotCrypto = mapOfAddedCurrencies[account.account] ?: listOf()
|
||||
val addedHotCrypto = mapOfAddedCurrencies[account.account].orEmpty()
|
||||
HotCryptoPortfolioData.Account(
|
||||
account = account,
|
||||
addedHotCrypto = addedHotCrypto,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ internal class OnrampAddTokenComponent @AssistedInject constructor(
|
|||
val cryptoCurrency: CryptoCurrency,
|
||||
val userWallet: UserWallet,
|
||||
val account: AccountStatus,
|
||||
val availableMorePortfolio: Boolean,
|
||||
val isMorePortfolioAvailable: Boolean,
|
||||
)
|
||||
|
||||
interface Callbacks {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ internal class OnrampAddTokenUiBuilder @Inject constructor(
|
|||
icon = accountIcon,
|
||||
name = portfolioName,
|
||||
isAccountMode = isAccountMode,
|
||||
isMultiChoice = tokenToAdd.availableMorePortfolio,
|
||||
isMultiChoice = tokenToAdd.isMorePortfolioAvailable,
|
||||
onClick = { params.callbacks.onChangePortfolioClick() },
|
||||
)
|
||||
}
|
||||
|
|
@ -76,7 +76,7 @@ internal class OnrampAddTokenUiBuilder @Inject constructor(
|
|||
val networkUM = createNetwork(tokenToAdd)
|
||||
val portfolioUM = createPortfolio(tokenToAdd)
|
||||
val currency = tokenToAdd.cryptoCurrency
|
||||
val tokenToAdd = TokenItemState.Content(
|
||||
val tokenItemState = TokenItemState.Content(
|
||||
id = currency.id.value,
|
||||
iconState = CryptoCurrencyToIconStateConverter().convert(currency),
|
||||
titleState = TokenItemState.TitleState.Content(stringReference(currency.name)),
|
||||
|
|
@ -87,7 +87,7 @@ internal class OnrampAddTokenUiBuilder @Inject constructor(
|
|||
onItemLongClick = null,
|
||||
)
|
||||
return AddTokenUM(
|
||||
tokenToAdd = tokenToAdd,
|
||||
tokenToAdd = tokenItemState,
|
||||
network = networkUM,
|
||||
portfolio = portfolioUM,
|
||||
button = button,
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ internal class OnrampAddToPortfolioModel @Inject constructor(
|
|||
|
||||
private val params: OnrampAddToPortfolioComponent.Params = paramsContainer.require()
|
||||
|
||||
val state: StateFlow<OnrampAddToPortfolioUM> get() = _state
|
||||
private val _state = MutableStateFlow(value = getInitialState())
|
||||
val state: StateFlow<OnrampAddToPortfolioUM>
|
||||
field = MutableStateFlow(value = getInitialState())
|
||||
|
||||
private fun getInitialState(): OnrampAddToPortfolioUM {
|
||||
return OnrampAddToPortfolioUM(
|
||||
|
|
@ -72,8 +72,11 @@ internal class OnrampAddToPortfolioModel @Inject constructor(
|
|||
private fun onAddClick() {
|
||||
modelScope.launch {
|
||||
changeAddButtonProgressStatus(isProgress = true)
|
||||
derivePublicKeysUseCase(params.userWalletId, listOfNotNull(params.cryptoCurrency)).getOrElse {
|
||||
Timber.e("Failed to derive public keys: $it")
|
||||
derivePublicKeysUseCase(
|
||||
userWalletId = params.userWalletId,
|
||||
currencies = listOf(params.cryptoCurrency),
|
||||
).getOrElse { throwable ->
|
||||
Timber.e("Failed to derive public keys: $throwable")
|
||||
|
||||
changeAddButtonProgressStatus(isProgress = false)
|
||||
}
|
||||
|
|
@ -88,8 +91,8 @@ internal class OnrampAddToPortfolioModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun changeAddButtonProgressStatus(isProgress: Boolean) {
|
||||
_state.update {
|
||||
it.copy(addButtonUM = it.addButtonUM.copy(isProgress = isProgress))
|
||||
state.update { prevState ->
|
||||
prevState.copy(addButtonUM = prevState.addButtonUM.copy(isProgress = isProgress))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,8 +69,8 @@ internal class OnrampAddTokenModel @Inject constructor(
|
|||
val cryptoCurrency = tokenToAdd.cryptoCurrency
|
||||
val accountId = tokenToAdd.account.accountId
|
||||
manageCryptoCurrenciesUseCase(accountId = accountId, add = cryptoCurrency)
|
||||
.onLeft {
|
||||
processError(error = it)
|
||||
.onLeft { throwable ->
|
||||
processError(error = throwable)
|
||||
uiState.value = um.toggleProgress(false)
|
||||
return@launch
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor(
|
|||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
country = config.country,
|
||||
launchSepa = params.launchSepa,
|
||||
isLaunchSepa = params.isLaunchSepa,
|
||||
onDismiss = {
|
||||
model.bottomSheetNavigation.dismiss()
|
||||
model.handleOnrampAvailable()
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ package com.tangem.features.onramp.main
|
|||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
internal interface OnrampMainComponent : ComposableContentComponent {
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ internal interface OnrampMainComponent : ComposableContentComponent {
|
|||
val source: OnrampSource,
|
||||
val openSettings: () -> Unit,
|
||||
val openRedirectPage: (quote: OnrampProviderWithQuote.Data) -> Unit,
|
||||
val launchSepa: Boolean,
|
||||
val isLaunchSepa: Boolean,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnrampMainComponent>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ internal sealed interface OnrampMainComponentUM {
|
|||
override val buyButtonConfig: BuyButtonConfig = BuyButtonConfig(
|
||||
text = resourceReference(R.string.common_buy),
|
||||
onClick = {},
|
||||
enabled = false,
|
||||
isEnabled = false,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -54,5 +54,5 @@ internal sealed interface OnrampMainComponentUM {
|
|||
internal data class BuyButtonConfig(
|
||||
val text: TextReference,
|
||||
val onClick: () -> Unit,
|
||||
val enabled: Boolean,
|
||||
val isEnabled: Boolean,
|
||||
)
|
||||
|
|
@ -72,7 +72,7 @@ internal class OnrampStateFactory(
|
|||
val contentState = state as? OnrampMainComponentUM.Content ?: return state
|
||||
|
||||
return contentState.copy(
|
||||
buyButtonConfig = contentState.buyButtonConfig.copy(enabled = false),
|
||||
buyButtonConfig = contentState.buyButtonConfig.copy(isEnabled = false),
|
||||
amountBlockState = contentState.amountBlockState.copy(
|
||||
amountFieldModel = contentState.amountBlockState.amountFieldModel.copy(isError = true),
|
||||
secondaryFieldModel = OnrampAmountSecondaryFieldUM.Error(
|
||||
|
|
@ -92,7 +92,7 @@ internal class OnrampStateFactory(
|
|||
return when (state) {
|
||||
is OnrampMainComponentUM.Content -> state.copy(
|
||||
topBarConfig = state.topBarConfig.copy(endButtonUM = endButton),
|
||||
buyButtonConfig = state.buyButtonConfig.copy(enabled = false),
|
||||
buyButtonConfig = state.buyButtonConfig.copy(isEnabled = false),
|
||||
amountBlockState = state.amountBlockState.copy(
|
||||
secondaryFieldModel = OnrampAmountSecondaryFieldUM.Content(TextReference.EMPTY),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ internal class OnrampAmountFieldChangeConverter(
|
|||
amountFieldModel = amountFieldModel,
|
||||
secondaryFieldModel = OnrampAmountSecondaryFieldUM.Content(TextReference.EMPTY),
|
||||
),
|
||||
buyButtonConfig = buyButtonConfig.copy(enabled = false),
|
||||
buyButtonConfig = buyButtonConfig.copy(isEnabled = false),
|
||||
providerBlockState = OnrampProviderBlockUM.Empty,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ internal class OnrampAmountStateFactory(
|
|||
return currentState.copy(
|
||||
amountBlockState = amountState.copy(secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading),
|
||||
providerBlockState = OnrampProviderBlockUM.Loading,
|
||||
buyButtonConfig = currentState.buyButtonConfig.copy(enabled = false),
|
||||
buyButtonConfig = currentState.buyButtonConfig.copy(isEnabled = false),
|
||||
errorNotification = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ internal class OnrampAmountStateFactory(
|
|||
secondaryFieldModel = quote.toSecondaryFieldUiModel(amountState) ?: amountState.secondaryFieldModel,
|
||||
),
|
||||
buyButtonConfig = currentState.buyButtonConfig.copy(
|
||||
enabled = quote is OnrampQuote.Data,
|
||||
isEnabled = quote is OnrampQuote.Data,
|
||||
onClick = {
|
||||
if (quote is OnrampQuote.Data) {
|
||||
onrampIntents.onBuyClick(
|
||||
|
|
@ -166,7 +166,7 @@ internal class OnrampAmountStateFactory(
|
|||
onLinkClick = onrampIntents::onLinkClick,
|
||||
),
|
||||
buyButtonConfig = currentState.buyButtonConfig.copy(
|
||||
enabled = providerResult is SelectProviderResult.ProviderWithQuote,
|
||||
isEnabled = providerResult is SelectProviderResult.ProviderWithQuote,
|
||||
onClick = {
|
||||
if (providerResult is SelectProviderResult.ProviderWithQuote) {
|
||||
onrampIntents.onBuyClick(
|
||||
|
|
|
|||
|
|
@ -14,17 +14,17 @@ import com.tangem.core.ui.components.fields.InputManager
|
|||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.onramp.*
|
||||
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
|
||||
import com.tangem.domain.onramp.model.OnrampAvailability
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.domain.onramp.model.OnrampQuote
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.onramp.main.OnrampMainComponent
|
||||
import com.tangem.features.onramp.main.entity.*
|
||||
|
|
@ -38,6 +38,7 @@ import com.tangem.utils.Provider
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.PeriodicTask
|
||||
import com.tangem.utils.coroutines.SingleTaskScheduler
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -69,35 +70,40 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
|
||||
private val params: OnrampMainComponent.Params = paramsContainer.require()
|
||||
|
||||
private var shouldForceChooseSepa = params.launchSepa
|
||||
private var shouldForceChooseSepa = params.isLaunchSepa
|
||||
private var currencyToRestore: OnrampCurrency? = null
|
||||
|
||||
val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<OnrampMainBottomSheetConfig> = SlotNavigation()
|
||||
|
||||
private val lastUpdateState = mutableStateOf<OnrampLastUpdate?>(null)
|
||||
private var userCountry: UserCountry? = null
|
||||
|
||||
@Suppress("PropertyUsedBeforeDeclaration")
|
||||
private val stateFactory = OnrampStateFactory(
|
||||
currentStateProvider = Provider { _state.value },
|
||||
currentStateProvider = Provider { state.value },
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
onrampIntents = this,
|
||||
)
|
||||
|
||||
val state: StateFlow<OnrampMainComponentUM>
|
||||
field = MutableStateFlow<OnrampMainComponentUM>(
|
||||
value = stateFactory.getInitialState(
|
||||
currency = params.cryptoCurrency.name,
|
||||
onClose = ::onCloseClick,
|
||||
),
|
||||
)
|
||||
|
||||
private val amountStateFactory = OnrampAmountStateFactory(
|
||||
currentStateProvider = Provider { _state.value },
|
||||
currentStateProvider = Provider { state.value },
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
onrampIntents = this,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
needApplyFCARestrictions = Provider { userCountry.needApplyFCARestrictions() },
|
||||
)
|
||||
private val _state: MutableStateFlow<OnrampMainComponentUM> = MutableStateFlow(
|
||||
value = stateFactory.getInitialState(
|
||||
currency = params.cryptoCurrency.name,
|
||||
onClose = ::onCloseClick,
|
||||
),
|
||||
)
|
||||
private val quotesTaskScheduler = SingleTaskScheduler<Unit>()
|
||||
val state: StateFlow<OnrampMainComponentUM> get() = _state.asStateFlow()
|
||||
val bottomSheetNavigation: SlotNavigation<OnrampMainBottomSheetConfig> = SlotNavigation()
|
||||
|
||||
private val lastUpdateState = mutableStateOf<OnrampLastUpdate?>(null)
|
||||
private var userCountry: UserCountry? = null
|
||||
private val quotesTaskScheduler = SingleTaskScheduler<Unit>()
|
||||
|
||||
init {
|
||||
userCountry = getUserCountryUseCase.invokeSync().getOrNull()
|
||||
|
|
@ -106,7 +112,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
clearOnrampCacheUseCase()
|
||||
|
||||
if (params.launchSepa) {
|
||||
if (params.isLaunchSepa) {
|
||||
currencyToRestore = onrampGetDefaultCurrencyUseCase.invoke().getOrNull()
|
||||
onrampSaveDefaultCurrencyUseCase.invoke(EUR_CURRENCY)
|
||||
}
|
||||
|
|
@ -132,7 +138,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun onProviderSelected(result: SelectProviderResult, isBestRate: Boolean) {
|
||||
_state.update { amountStateFactory.getAmountSecondaryUpdatedState(result, isBestRate) }
|
||||
state.update { amountStateFactory.getAmountSecondaryUpdatedState(result, isBestRate) }
|
||||
|
||||
if (result.paymentMethod.id != SEPA_METHOD_ID) {
|
||||
shouldForceChooseSepa = false
|
||||
|
|
@ -164,9 +170,9 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
ifRight = { country ->
|
||||
if (country == null) return@onEach
|
||||
|
||||
val wasInitialLoading = _state.value is OnrampMainComponentUM.InitialLoading
|
||||
_state.update {
|
||||
if (it is OnrampMainComponentUM.InitialLoading) {
|
||||
val wasInitialLoading = state.value is OnrampMainComponentUM.InitialLoading
|
||||
state.update { prevState ->
|
||||
if (prevState is OnrampMainComponentUM.InitialLoading) {
|
||||
stateFactory.getReadyState(country.defaultCurrency)
|
||||
} else {
|
||||
amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency)
|
||||
|
|
@ -175,7 +181,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
|
||||
updatePairsAndQuotes()
|
||||
|
||||
if (wasInitialLoading && params.launchSepa) {
|
||||
if (wasInitialLoading && params.isLaunchSepa) {
|
||||
onAmountValueChanged(value = PREDEFINED_SEPA_AMOUNT, isValuePasted = true)
|
||||
}
|
||||
},
|
||||
|
|
@ -188,20 +194,25 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
amountInputManager.query
|
||||
.filter(String::isNotEmpty)
|
||||
.collectLatest { _ ->
|
||||
_state.update { amountStateFactory.getAmountSecondaryLoadingState() }
|
||||
state.update { amountStateFactory.getAmountSecondaryLoadingState() }
|
||||
startLoadingQuotes()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updatePairsAndQuotes() {
|
||||
val state = state.value as? OnrampMainComponentUM.Content
|
||||
state.update { prevState ->
|
||||
val contentState = state.value as? OnrampMainComponentUM.Content ?: return@update prevState
|
||||
|
||||
if (!state?.amountBlockState?.amountFieldModel?.fiatValue.isNullOrEmpty()) {
|
||||
_state.update { amountStateFactory.getAmountSecondaryLoadingState() }
|
||||
if (contentState.amountBlockState.amountFieldModel.fiatValue.isNotEmpty()) {
|
||||
amountStateFactory.getAmountSecondaryLoadingState()
|
||||
} else {
|
||||
prevState
|
||||
}
|
||||
}
|
||||
|
||||
fetchPairsUseCase.invoke(userWallet, params.cryptoCurrency).fold(
|
||||
ifLeft = ::handleOnrampError,
|
||||
ifRight = { _state.update { amountStateFactory.getAmountSecondaryResetState() } },
|
||||
ifRight = { state.update { amountStateFactory.getAmountSecondaryResetState() } },
|
||||
)
|
||||
startLoadingQuotes()
|
||||
}
|
||||
|
|
@ -209,7 +220,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
private fun handleOnrampError(onrampError: OnrampError) {
|
||||
Timber.e(onrampError.toString())
|
||||
sendOnrampErrorAnalytic(onrampError)
|
||||
_state.update { stateFactory.getOnrampErrorState(onrampError) }
|
||||
state.update { stateFactory.getOnrampErrorState(onrampError) }
|
||||
}
|
||||
|
||||
private fun startLoadingQuotes() {
|
||||
|
|
@ -221,12 +232,15 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
return PeriodicTask(
|
||||
delay = UPDATE_DELAY,
|
||||
task = {
|
||||
runCatching {
|
||||
val content = state.value as? OnrampMainComponentUM.Content ?: return@runCatching
|
||||
if (content.amountBlockState.amountFieldModel.fiatAmount.value.isNullOrZero()) return@runCatching
|
||||
runSuspendCatching {
|
||||
val content = state.value as? OnrampMainComponentUM.Content ?: return@runSuspendCatching
|
||||
val amountBlockState = content.amountBlockState
|
||||
if (amountBlockState.amountFieldModel.fiatAmount.value.isNullOrZero()) {
|
||||
return@runSuspendCatching
|
||||
}
|
||||
fetchQuotesUseCase.invoke(
|
||||
userWallet = userWallet,
|
||||
amount = content.amountBlockState.amountFieldModel.fiatAmount,
|
||||
amount = amountBlockState.amountFieldModel.fiatAmount,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
).onLeft(::handleOnrampError)
|
||||
}
|
||||
|
|
@ -249,7 +263,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onAmountValueChanged(value: String, isValuePasted: Boolean) {
|
||||
_state.update { amountStateFactory.getOnAmountValueChange(value, isValuePasted) }
|
||||
state.update { amountStateFactory.getOnAmountValueChange(value, isValuePasted) }
|
||||
modelScope.launch { amountInputManager.update(value) }
|
||||
}
|
||||
|
||||
|
|
@ -290,7 +304,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onRefresh() {
|
||||
_state.update {
|
||||
state.update {
|
||||
stateFactory.getInitialState(
|
||||
currency = params.cryptoCurrency.name,
|
||||
onClose = router::pop,
|
||||
|
|
@ -310,7 +324,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
quotesTaskScheduler.cancelTask()
|
||||
|
||||
modelScope.launch {
|
||||
if (params.launchSepa) {
|
||||
if (params.isLaunchSepa) {
|
||||
currencyToRestore?.let { onrampSaveDefaultCurrencyUseCase.invoke(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -329,11 +343,11 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
val quote = selectOrUpdateQuote(quotes)
|
||||
|
||||
if (quote == null) {
|
||||
_state.update { stateFactory.getErrorState(onRefresh = ::onRetryQuotes) }
|
||||
state.update { stateFactory.getErrorState(onRefresh = ::onRetryQuotes) }
|
||||
lastUpdateState.value = null
|
||||
return
|
||||
}
|
||||
_state.update { amountStateFactory.getAmountSecondaryUpdatedState(quote = quote) }
|
||||
state.update { amountStateFactory.getAmountSecondaryUpdatedState(quote = quote) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -346,7 +360,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
private fun selectOrUpdateQuote(quotes: List<OnrampQuote>): OnrampQuote? {
|
||||
val quoteToCheck = quotes.firstOrNull { it !is OnrampQuote.Error }
|
||||
|
||||
val bestSepaQuote = if (params.launchSepa && shouldForceChooseSepa) {
|
||||
val bestSepaQuote = if (params.isLaunchSepa && shouldForceChooseSepa) {
|
||||
quotes.filterIsInstance<OnrampQuote.Data>()
|
||||
.filter { it.paymentMethod.id == SEPA_METHOD_ID }
|
||||
.maxByOrNull { it.toAmount.value }
|
||||
|
|
@ -362,9 +376,9 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
val providerState = state?.providerBlockState as? OnrampProviderBlockUM.Content
|
||||
|
||||
// Get current selected quote to update
|
||||
val lastSelectedQuote = quotes.firstOrNull {
|
||||
it.provider.id == providerState?.providerId &&
|
||||
it.paymentMethod.id == providerState.paymentMethod.id
|
||||
val lastSelectedQuote = quotes.firstOrNull { quote ->
|
||||
quote.provider.id == providerState?.providerId &&
|
||||
quote.paymentMethod.id == providerState.paymentMethod.id
|
||||
}
|
||||
|
||||
// Check if selected updated quote is not error
|
||||
|
|
@ -392,18 +406,20 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
shouldForceChooseSepa = false
|
||||
}
|
||||
|
||||
_state.update {
|
||||
state.update {
|
||||
amountStateFactory.getUpdatedProviderState(selectedQuote = quote, quotes = quotes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRetryQuotes() {
|
||||
_state.update {
|
||||
(it as? OnrampMainComponentUM.Content)?.copy(
|
||||
state.update { prevState ->
|
||||
(prevState as? OnrampMainComponentUM.Content)?.copy(
|
||||
errorNotification = null,
|
||||
providerBlockState = OnrampProviderBlockUM.Loading,
|
||||
amountBlockState = it.amountBlockState.copy(secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading),
|
||||
) ?: it
|
||||
amountBlockState = prevState.amountBlockState.copy(
|
||||
secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading,
|
||||
),
|
||||
) ?: prevState
|
||||
}
|
||||
startLoadingQuotes()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ internal fun OnrampButtonComponent(state: OnrampMainComponentUM) {
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResourceSafe(id = R.string.common_buy),
|
||||
onClick = state.buyButtonConfig.onClick,
|
||||
enabled = state.buyButtonConfig.enabled,
|
||||
enabled = state.buyButtonConfig.isEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -78,19 +78,23 @@ private fun OnrampTosText(provider: OnrampProviderBlockUM.Content?) {
|
|||
textAlign = TextAlign.Center,
|
||||
),
|
||||
onClick = { offset ->
|
||||
clickableAnnotation.getStringAnnotations(
|
||||
val tosAnnotations = clickableAnnotation.getStringAnnotations(
|
||||
tag = TERMS_OF_USE_KEY,
|
||||
start = offset,
|
||||
end = offset,
|
||||
).firstOrNull()?.let {
|
||||
)
|
||||
|
||||
if (tosAnnotations.any()) {
|
||||
state.onLinkClick(termsOfUseLink)
|
||||
}
|
||||
|
||||
clickableAnnotation.getStringAnnotations(
|
||||
val privacyPolicyAnnotations = clickableAnnotation.getStringAnnotations(
|
||||
tag = PRIVACY_POLICY_KEY,
|
||||
start = offset,
|
||||
end = offset,
|
||||
).firstOrNull()?.let {
|
||||
)
|
||||
|
||||
if (privacyPolicyAnnotations.any()) {
|
||||
state.onLinkClick(privacyPolicyLink)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ internal class DefaultOnrampV2MainComponent @AssistedInject constructor(
|
|||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
country = config.country,
|
||||
launchSepa = false,
|
||||
isLaunchSepa = false,
|
||||
onDismiss = {
|
||||
model.bottomSheetNavigation.dismiss()
|
||||
model.handleOnrampAvailable()
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import com.tangem.utils.Provider
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.PeriodicTask
|
||||
import com.tangem.utils.coroutines.SingleTaskScheduler
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -54,41 +55,43 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
OnrampAmountButtonUMStateFactory()
|
||||
}
|
||||
|
||||
private val onrampOffersStateFactory: OnrampOffersStateFactory by lazy(LazyThreadSafetyMode.NONE) {
|
||||
OnrampOffersStateFactory(
|
||||
currentStateProvider = Provider { _state.value },
|
||||
onrampIntents = this,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("PropertyUsedBeforeDeclaration")
|
||||
private val stateFactory: OnrampV2StateFactory by lazy(LazyThreadSafetyMode.NONE) {
|
||||
OnrampV2StateFactory(
|
||||
currentStateProvider = Provider { _state.value },
|
||||
currentStateProvider = Provider { state.value },
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
onrampIntents = this,
|
||||
onrampAmountButtonUMStateFactory = onrampAmountButtonUMStateFactory,
|
||||
)
|
||||
}
|
||||
|
||||
val state: StateFlow<OnrampV2MainComponentUM>
|
||||
field = MutableStateFlow<OnrampV2MainComponentUM>(
|
||||
value = stateFactory.getInitialState(
|
||||
currency = params.cryptoCurrency.name,
|
||||
onClose = ::onCloseClick,
|
||||
openSettings = ::openSettings,
|
||||
),
|
||||
)
|
||||
|
||||
private val amountStateFactory: OnrampV2AmountStateFactory by lazy(LazyThreadSafetyMode.NONE) {
|
||||
OnrampV2AmountStateFactory(
|
||||
currentStateProvider = Provider { _state.value },
|
||||
currentStateProvider = Provider { state.value },
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
onrampIntents = this,
|
||||
onrampAmountButtonUMStateFactory = onrampAmountButtonUMStateFactory,
|
||||
)
|
||||
}
|
||||
|
||||
private val _state: MutableStateFlow<OnrampV2MainComponentUM> = MutableStateFlow(
|
||||
value = stateFactory.getInitialState(
|
||||
currency = params.cryptoCurrency.name,
|
||||
onClose = ::onCloseClick,
|
||||
openSettings = ::openSettings,
|
||||
),
|
||||
)
|
||||
private val onrampOffersStateFactory: OnrampOffersStateFactory by lazy(LazyThreadSafetyMode.NONE) {
|
||||
OnrampOffersStateFactory(
|
||||
currentStateProvider = Provider { state.value },
|
||||
onrampIntents = this,
|
||||
)
|
||||
}
|
||||
|
||||
private val quotesTaskScheduler = SingleTaskScheduler<Unit>()
|
||||
|
||||
val state: StateFlow<OnrampV2MainComponentUM> get() = _state.asStateFlow()
|
||||
val bottomSheetNavigation: SlotNavigation<OnrampV2MainBottomSheetConfig> = SlotNavigation()
|
||||
val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
|
||||
|
||||
|
|
@ -112,7 +115,7 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onAmountValueChanged(value: String) {
|
||||
_state.update { amountStateFactory.getOnAmountValueChange(value) }
|
||||
state.update { amountStateFactory.getOnAmountValueChange(value) }
|
||||
modelScope.launch { amountInputManager.update(value) }
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +156,7 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onRefresh() {
|
||||
_state.update {
|
||||
state.update {
|
||||
stateFactory.getInitialState(
|
||||
currency = params.cryptoCurrency.name,
|
||||
onClose = router::pop,
|
||||
|
|
@ -185,12 +188,16 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
return PeriodicTask(
|
||||
delay = UPDATE_DELAY,
|
||||
task = {
|
||||
runCatching {
|
||||
val content = state.value as? OnrampV2MainComponentUM.Content ?: return@runCatching
|
||||
if (content.amountBlockState.amountFieldModel.fiatAmount.value.isNullOrZero()) return@runCatching
|
||||
runSuspendCatching {
|
||||
val amountBlockState = (state.value as? OnrampV2MainComponentUM.Content)?.amountBlockState
|
||||
?: return@runSuspendCatching
|
||||
|
||||
val fiatAmount = amountBlockState.amountFieldModel.fiatAmount
|
||||
if (fiatAmount.value.isNullOrZero()) return@runSuspendCatching
|
||||
|
||||
fetchQuotesUseCase.invoke(
|
||||
userWallet = userWallet,
|
||||
amount = content.amountBlockState.amountFieldModel.fiatAmount,
|
||||
amount = amountBlockState.amountFieldModel.fiatAmount,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
).onLeft(::handleOnrampError)
|
||||
}
|
||||
|
|
@ -230,7 +237,7 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
ifLeft = ::handleOnrampError,
|
||||
ifRight = { offers ->
|
||||
if (offers.isNotEmpty()) {
|
||||
_state.update { onrampOffersStateFactory.getOffersState(offers) }
|
||||
state.update { onrampOffersStateFactory.getOffersState(offers) }
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -252,8 +259,8 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
ifLeft = ::handleOnrampError,
|
||||
ifRight = { country ->
|
||||
if (country == null) return@onEach
|
||||
_state.update {
|
||||
when (it) {
|
||||
state.update { prevState ->
|
||||
when (prevState) {
|
||||
is OnrampV2MainComponentUM.Content -> {
|
||||
amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency)
|
||||
}
|
||||
|
|
@ -285,27 +292,29 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
sendOnrampQuotesErrorAnalytic(quotes)
|
||||
when {
|
||||
quotes.isEmpty() -> {
|
||||
_state.update { stateFactory.getErrorState(onRefresh = ::onRetryQuotes) }
|
||||
state.update { stateFactory.getErrorState(onRefresh = ::onRetryQuotes) }
|
||||
}
|
||||
quotes.all { it is OnrampQuote.AmountError } -> {
|
||||
_state.update { amountStateFactory.getSecondaryFieldAmountErrorState(quotes) }
|
||||
state.update { amountStateFactory.getSecondaryFieldAmountErrorState(quotes) }
|
||||
}
|
||||
quotes.none { it is OnrampQuote.Data } -> {
|
||||
_state.update { stateFactory.getErrorState(onRefresh = ::onRetryQuotes) }
|
||||
state.update { stateFactory.getErrorState(onRefresh = ::onRetryQuotes) }
|
||||
}
|
||||
else -> {
|
||||
_state.update { amountStateFactory.getAmountSecondaryFieldResetState() }
|
||||
state.update { amountStateFactory.getAmountSecondaryFieldResetState() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRetryQuotes() {
|
||||
_state.update {
|
||||
(it as? OnrampV2MainComponentUM.Content)?.copy(
|
||||
state.update { prevState ->
|
||||
(prevState as? OnrampV2MainComponentUM.Content)?.copy(
|
||||
errorNotification = null,
|
||||
offersBlockState = OnrampOffersBlockUM.Loading,
|
||||
amountBlockState = it.amountBlockState.copy(secondaryFieldModel = OnrampSecondaryFieldErrorUM.Empty),
|
||||
) ?: it
|
||||
amountBlockState = prevState.amountBlockState.copy(
|
||||
secondaryFieldModel = OnrampSecondaryFieldErrorUM.Empty,
|
||||
),
|
||||
) ?: prevState
|
||||
}
|
||||
startLoadingQuotes()
|
||||
}
|
||||
|
|
@ -314,7 +323,7 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
fetchPairsUseCase.invoke(userWallet, params.cryptoCurrency).fold(
|
||||
ifLeft = ::handleOnrampError,
|
||||
ifRight = {
|
||||
_state.update {
|
||||
state.update {
|
||||
amountStateFactory.getAmountSecondaryFieldResetState()
|
||||
}
|
||||
startLoadingQuotes()
|
||||
|
|
@ -324,7 +333,7 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
|
||||
private fun handleOnrampError(onrampError: OnrampError) {
|
||||
Timber.e(onrampError.toString())
|
||||
_state.update { stateFactory.getOnrampErrorState(onrampError) }
|
||||
state.update { stateFactory.getOnrampErrorState(onrampError) }
|
||||
}
|
||||
|
||||
private fun sendOnrampQuotesErrorAnalytic(quotes: List<OnrampQuote>) {
|
||||
|
|
@ -361,22 +370,26 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
onrampOfferAdvantagesUM: OnrampOfferAdvantagesUM,
|
||||
categoryUM: OnrampOfferCategoryUM,
|
||||
) {
|
||||
when (categoryUM) {
|
||||
val event = when (categoryUM) {
|
||||
OnrampOfferCategoryUM.RecentlyUsed -> {
|
||||
OnrampAnalyticsEvent.RecentlyBuyClicked(
|
||||
tokenSymbol = params.cryptoCurrency.symbol,
|
||||
providerName = quote.provider.info.name,
|
||||
paymentMethod = quote.paymentMethod.name,
|
||||
).let(analyticsEventHandler::send)
|
||||
)
|
||||
}
|
||||
OnrampOfferCategoryUM.Recommended -> {
|
||||
onrampOfferAdvantagesUM.toAnalyticsEvent(
|
||||
cryptoCurrencySymbol = params.cryptoCurrency.symbol,
|
||||
providerName = quote.provider.info.name,
|
||||
paymentMethodName = quote.paymentMethod.name,
|
||||
)?.let(analyticsEventHandler::send)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (event != null) {
|
||||
analyticsEventHandler.send(event)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -25,34 +25,28 @@ import com.tangem.features.onramp.mainv2.entity.OnrampV2AmountButtonUMState
|
|||
import com.tangem.features.onramp.mainv2.entity.OnrampV2MainComponentUM
|
||||
|
||||
@Composable
|
||||
internal fun OnrampFooterContent(
|
||||
state: OnrampV2MainComponentUM.Content,
|
||||
boxScope: BoxScope,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
boxScope.apply {
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.align(Alignment.BottomCenter),
|
||||
visible = state.offersBlockState is OnrampOffersBlockUM.Empty,
|
||||
enter = slideInVertically(
|
||||
initialOffsetY = { it },
|
||||
animationSpec = tween(durationMillis = 300),
|
||||
),
|
||||
exit = slideOutVertically(
|
||||
targetOffsetY = { it },
|
||||
animationSpec = tween(durationMillis = 300),
|
||||
),
|
||||
label = "Footer block animation",
|
||||
internal fun BoxScope.OnrampFooterContent(state: OnrampV2MainComponentUM.Content, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(
|
||||
modifier = modifier
|
||||
.imePadding()
|
||||
.align(Alignment.BottomCenter),
|
||||
visible = state.offersBlockState is OnrampOffersBlockUM.Empty,
|
||||
enter = slideInVertically(
|
||||
initialOffsetY = { it },
|
||||
animationSpec = tween(durationMillis = 300),
|
||||
),
|
||||
exit = slideOutVertically(
|
||||
targetOffsetY = { it },
|
||||
animationSpec = tween(durationMillis = 300),
|
||||
),
|
||||
label = "Footer block animation",
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
SpacerH(16.dp)
|
||||
OnrampAmountButtons(state = state.onrampAmountButtonUMState)
|
||||
}
|
||||
SpacerH(16.dp)
|
||||
OnrampAmountButtons(state = state.onrampAmountButtonUMState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,10 +60,7 @@ internal fun OnrampNewMainComponentContent(state: OnrampV2MainComponentUM, modif
|
|||
}
|
||||
|
||||
if (state is OnrampV2MainComponentUM.Content) {
|
||||
OnrampFooterContent(
|
||||
state = state,
|
||||
boxScope = this,
|
||||
)
|
||||
OnrampFooterContent(state = state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,12 +79,12 @@ internal fun OnrampOffersContent(state: OnrampOffersBlockUM) {
|
|||
}
|
||||
}
|
||||
|
||||
state.onrampAllOffersButtonConfig?.let {
|
||||
if (state.onrampAllOffersButtonConfig != null) {
|
||||
SpacerH(12.dp)
|
||||
SecondaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = it.title.resolveReference(),
|
||||
onClick = it.onClick,
|
||||
text = state.onrampAllOffersButtonConfig.title.resolveReference(),
|
||||
onClick = state.onrampAllOffersButtonConfig.onClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -212,7 +212,8 @@ private fun RateBlock(rate: String, diff: TextReference?, isOfferUnavailable: Bo
|
|||
color = if (isOfferUnavailable) TangemTheme.colors.text.tertiary else TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.testTag(OnrampOffersBlockTestTags.OFFER_TOKEN_AMOUNT),
|
||||
)
|
||||
diff?.let {
|
||||
|
||||
if (diff != null) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
|
|
@ -220,7 +221,7 @@ private fun RateBlock(rate: String, diff: TextReference?, isOfferUnavailable: Bo
|
|||
shape = RoundedCornerShape(4.dp),
|
||||
)
|
||||
.padding(horizontal = 4.dp),
|
||||
text = it.resolveReference(),
|
||||
text = diff.resolveReference(),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.warning,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ internal data class ProviderListPaymentMethodUM(
|
|||
val id: String,
|
||||
val name: String,
|
||||
val imageUrl: String,
|
||||
val enabled: Boolean,
|
||||
val isEnabled: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -36,7 +36,6 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
|
@ -55,10 +54,11 @@ internal class SelectProviderModel @Inject constructor(
|
|||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<SelectPaymentAndProviderUM> get() = _state.asStateFlow()
|
||||
val state: StateFlow<SelectPaymentAndProviderUM>
|
||||
field = MutableStateFlow(getInitialState())
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<ProviderListBottomSheetConfig> = SlotNavigation()
|
||||
private val params: SelectProviderComponent.Params = paramsContainer.require()
|
||||
private val _state = MutableStateFlow(getInitialState())
|
||||
|
||||
private var userCountry: UserCountry? = null
|
||||
|
||||
|
|
@ -84,18 +84,18 @@ internal class SelectProviderModel @Inject constructor(
|
|||
val filteredEmptyMethods = methods.mapNotNull { method ->
|
||||
val providers = getOnrampProviderWithQuoteUseCase(method).getOrNull()
|
||||
if (!providers.isNullOrEmpty()) {
|
||||
val allErrorProviders = providers.all {
|
||||
val isAllErrorProviders = providers.all {
|
||||
it is OnrampProviderWithQuote.Unavailable.NotSupportedPaymentMethod
|
||||
}
|
||||
PaymentProviderUM(
|
||||
paymentMethod = method,
|
||||
providers = providers.toProvidersListItems(),
|
||||
).takeIf { !allErrorProviders }
|
||||
).takeIf { !isAllErrorProviders }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
_state.value = state.value.copy(
|
||||
state.value = state.value.copy(
|
||||
paymentMethods = filteredEmptyMethods.toPersistentList(),
|
||||
isPaymentMethodClickEnabled = filteredEmptyMethods.isNotEmpty(),
|
||||
)
|
||||
|
|
@ -106,7 +106,7 @@ internal class SelectProviderModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
getOnrampProviderWithQuoteUseCase.invoke(paymentMethod)
|
||||
.onRight { quotes ->
|
||||
_state.update { state ->
|
||||
state.update { state ->
|
||||
state.copy(
|
||||
selectedPaymentMethod = state.selectedPaymentMethod.copy(
|
||||
providers = quotes.toProvidersListItems(),
|
||||
|
|
@ -163,7 +163,7 @@ internal class SelectProviderModel @Inject constructor(
|
|||
val firstProvider = methodContainer.providers.firstOrNull()
|
||||
analyticsEventHandler.send(OnrampAnalyticsEvent.OnPaymentMethodChosen(paymentMethod = paymentMethod.name))
|
||||
modelScope.launch {
|
||||
_state.update { state ->
|
||||
state.update { state ->
|
||||
state.copy(
|
||||
selectedPaymentMethod = state.selectedPaymentMethod.copy(
|
||||
paymentMethod = paymentMethod,
|
||||
|
|
@ -279,7 +279,7 @@ internal class SelectProviderModel @Inject constructor(
|
|||
tokenSymbol = params.cryptoCurrency.symbol,
|
||||
),
|
||||
)
|
||||
_state.update {
|
||||
state.update {
|
||||
it.copy(selectedProviderId = result.provider.id)
|
||||
}
|
||||
params.onProviderClick(result, isBestRate)
|
||||
|
|
@ -303,15 +303,15 @@ internal class SelectProviderModel @Inject constructor(
|
|||
* 1. Highest rate
|
||||
* 2. Smallest difference between entered amount and required min/max amount
|
||||
*/
|
||||
private fun List<OnrampProviderWithQuote>.sortByRate() = sortedByDescending {
|
||||
when (it) {
|
||||
is OnrampProviderWithQuote.Data -> it.toAmount.value
|
||||
private fun List<OnrampProviderWithQuote>.sortByRate() = sortedByDescending { quote ->
|
||||
when (quote) {
|
||||
is OnrampProviderWithQuote.Data -> quote.toAmount.value
|
||||
|
||||
// negative difference to sort both when data and unavailable is present
|
||||
is OnrampProviderWithQuote.Unavailable.AmountError -> {
|
||||
when (val error = it.quoteError.error) {
|
||||
is OnrampError.AmountError.TooSmallError -> it.quoteError.fromAmount.value - error.requiredAmount
|
||||
is OnrampError.AmountError.TooBigError -> error.requiredAmount - it.quoteError.fromAmount.value
|
||||
when (val error = quote.quoteError.error) {
|
||||
is OnrampError.AmountError.TooSmallError -> quote.quoteError.fromAmount.value - error.requiredAmount
|
||||
is OnrampError.AmountError.TooBigError -> error.requiredAmount - quote.quoteError.fromAmount.value
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ internal object SelectProviderPreviewData {
|
|||
termsOfUseLink = null,
|
||||
privacyPolicyLink = null,
|
||||
),
|
||||
paymentMethods = listOf(),
|
||||
paymentMethods = emptyList(),
|
||||
)
|
||||
|
||||
private val providerListUM = PaymentProviderUM(
|
||||
|
|
|
|||
|
|
@ -83,14 +83,14 @@ internal class OnrampRedirectModel @Inject constructor(
|
|||
isDarkTheme = isDarkTheme,
|
||||
)
|
||||
.onLeft(::handleError)
|
||||
.onRight {
|
||||
latestOnrampTransaction = it
|
||||
.onRight { transaction ->
|
||||
latestOnrampTransaction = transaction
|
||||
|
||||
// Workaround to open Unlimit provider in external browser instead of chrome custom tabs
|
||||
if (params.onrampProviderWithQuote.provider.id.equals(UNLIMIT_PROVIDER_ID, ignoreCase = true)) {
|
||||
urlOpener.openUrlExternalBrowser(it.redirectUrl)
|
||||
urlOpener.openUrlExternalBrowser(transaction.redirectUrl)
|
||||
} else {
|
||||
urlOpener.openUrl(it.redirectUrl)
|
||||
urlOpener.openUrl(transaction.redirectUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ internal class DefaultOnrampComponent @AssistedInject constructor(
|
|||
childFactory = ::screenChild,
|
||||
)
|
||||
|
||||
@Suppress("ReusedModifierInstance")
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val childStack by contentStack.subscribeAsState()
|
||||
|
|
@ -78,10 +79,10 @@ internal class DefaultOnrampComponent @AssistedInject constructor(
|
|||
cryptoCurrency = params.cryptoCurrency,
|
||||
openSettings = { navigation.push(OnrampChild.Settings) },
|
||||
source = params.source,
|
||||
openRedirectPage = {
|
||||
openRedirectPage = { quote ->
|
||||
navigation.push(
|
||||
OnrampChild.RedirectPage(
|
||||
quote = it,
|
||||
quote = quote,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
),
|
||||
)
|
||||
|
|
@ -96,15 +97,15 @@ internal class DefaultOnrampComponent @AssistedInject constructor(
|
|||
cryptoCurrency = params.cryptoCurrency,
|
||||
openSettings = { navigation.push(OnrampChild.Settings) },
|
||||
source = params.source,
|
||||
openRedirectPage = {
|
||||
openRedirectPage = { onrampProviderWithQuoteData ->
|
||||
navigation.push(
|
||||
OnrampChild.RedirectPage(
|
||||
quote = it,
|
||||
quote = onrampProviderWithQuoteData,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
),
|
||||
)
|
||||
},
|
||||
launchSepa = params.shouldLaunchSepa,
|
||||
isLaunchSepa = params.shouldLaunchSepa,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.utils.transformer.Transformer
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
internal class CountryListUMController(
|
||||
|
|
@ -14,16 +13,17 @@ internal class CountryListUMController(
|
|||
loadingItems: ImmutableList<CountryItemState.Loading>,
|
||||
) {
|
||||
|
||||
val state: StateFlow<CountryListUM> get() = _state.asStateFlow()
|
||||
private val _state: MutableStateFlow<CountryListUM> =
|
||||
MutableStateFlow(value = CountryListUM.Loading(searchBarUM = searchBarUM, items = loadingItems))
|
||||
val state: StateFlow<CountryListUM>
|
||||
field = MutableStateFlow<CountryListUM>(
|
||||
value = CountryListUM.Loading(searchBarUM = searchBarUM, items = loadingItems),
|
||||
)
|
||||
|
||||
fun update(transformer: Transformer<CountryListUM>) {
|
||||
_state.update(transformer::transform)
|
||||
state.update(transformer::transform)
|
||||
}
|
||||
|
||||
fun update(transformer: SearchBarUMTransformer) {
|
||||
_state.update { prevState ->
|
||||
state.update { prevState ->
|
||||
val searchBarUM = transformer.transform(prevState.searchBarUM)
|
||||
prevState.copySealed(searchBarUM)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,13 +52,13 @@ internal class OnrampSelectCountryModel @Inject constructor(
|
|||
private val params: SelectCountryComponent.Params = paramsContainer.require()
|
||||
private val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
|
||||
|
||||
val state: StateFlow<CountryListUM> get() = controller.state
|
||||
|
||||
private val controller = CountryListUMController(
|
||||
searchBarUM = createSearchBarUM(),
|
||||
loadingItems = loadingItems,
|
||||
)
|
||||
|
||||
val state: StateFlow<CountryListUM> get() = controller.state
|
||||
|
||||
init {
|
||||
analyticsEventHandler.send(OnrampAnalyticsEvent.SelectResidenceOpened)
|
||||
updateCountriesList()
|
||||
|
|
@ -69,7 +69,7 @@ internal class OnrampSelectCountryModel @Inject constructor(
|
|||
params.onDismiss(isCountrySelected)
|
||||
}
|
||||
|
||||
private suspend fun subscribeOnUpdateState() {
|
||||
private fun subscribeOnUpdateState() {
|
||||
combine(
|
||||
flow = getOnrampCountriesUseCase(),
|
||||
flow2 = getOnrampCountryUseCase(),
|
||||
|
|
|
|||
|
|
@ -6,27 +6,26 @@ import com.tangem.utils.transformer.Transformer
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
internal class CurrencyListController(
|
||||
currencySearchBarUM: SearchBarUM,
|
||||
loadingSections: ImmutableList<CurrenciesSection<CurrencyItemState.Loading>>,
|
||||
) {
|
||||
val state: StateFlow<CurrenciesListUM> get() = _state.asStateFlow()
|
||||
private val _state: MutableStateFlow<CurrenciesListUM> = MutableStateFlow(
|
||||
value = CurrenciesListUM.Loading(
|
||||
searchBarUM = currencySearchBarUM,
|
||||
sections = loadingSections,
|
||||
),
|
||||
)
|
||||
val state: StateFlow<CurrenciesListUM>
|
||||
field = MutableStateFlow<CurrenciesListUM>(
|
||||
value = CurrenciesListUM.Loading(
|
||||
searchBarUM = currencySearchBarUM,
|
||||
sections = loadingSections,
|
||||
),
|
||||
)
|
||||
|
||||
fun update(transformer: Transformer<CurrenciesListUM>) {
|
||||
_state.update(transformer::transform)
|
||||
state.update(transformer::transform)
|
||||
}
|
||||
|
||||
fun update(transformer: SearchBarUMTransformer) {
|
||||
_state.update { prevState ->
|
||||
state.update { prevState ->
|
||||
val searchBarUM = transformer.transform(prevState.searchBarUM)
|
||||
prevState.copySealed(searchBarUM)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,14 +46,14 @@ internal class OnrampSelectCurrencyModel @Inject constructor(
|
|||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<CurrenciesListUM> get() = controller.state
|
||||
|
||||
private val params: SelectCurrencyComponent.Params = paramsContainer.require()
|
||||
private val controller = CurrencyListController(
|
||||
currencySearchBarUM = createSearchBarUM(),
|
||||
loadingSections = loadingSections,
|
||||
)
|
||||
|
||||
val state: StateFlow<CurrenciesListUM> get() = controller.state
|
||||
private val params: SelectCurrencyComponent.Params = paramsContainer.require()
|
||||
|
||||
init {
|
||||
updateCurrenciesList()
|
||||
subscribeOnUpdateState()
|
||||
|
|
|
|||
|
|
@ -49,12 +49,11 @@ internal class OnrampOperationModel @Inject constructor(
|
|||
private val rampStateManager: RampStateManager,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<OnrampOperationUM> get() = _state
|
||||
val state: StateFlow<OnrampOperationUM>
|
||||
field = MutableStateFlow(value = getInitialState())
|
||||
|
||||
private val params: Params = paramsContainer.require()
|
||||
|
||||
private val _state = MutableStateFlow(value = getInitialState())
|
||||
|
||||
private val selectedUserWallet = getWalletsUseCase.invokeSync()
|
||||
.first { it.walletId == params.userWalletId }
|
||||
|
||||
|
|
@ -176,7 +175,7 @@ internal class OnrampOperationModel @Inject constructor(
|
|||
|
||||
fun onTokenListInitialized() {
|
||||
// Makes HotCrypto tokens visible when token list is initialized to synchronize UI
|
||||
_state.update {
|
||||
state.update {
|
||||
it.copy(isHotCryptoVisible = true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,11 +29,12 @@ internal class OnrampSettingsModel @Inject constructor(
|
|||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<OnrampSettingsUM> get() = _state
|
||||
val state: StateFlow<OnrampSettingsUM>
|
||||
field = MutableStateFlow(getInitialState())
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<OnrampSettingsConfig> = SlotNavigation()
|
||||
|
||||
private val params: OnrampSettingsComponent.Params = paramsContainer.require()
|
||||
private val _state: MutableStateFlow<OnrampSettingsUM> = MutableStateFlow(getInitialState())
|
||||
|
||||
init {
|
||||
analyticsEventHandler.send(OnrampAnalyticsEvent.SettingsOpened)
|
||||
|
|
@ -52,7 +53,7 @@ internal class OnrampSettingsModel @Inject constructor(
|
|||
analyticsEventHandler.sendOnrampErrorEvent(it, params.cryptoCurrency.symbol)
|
||||
},
|
||||
ifRight = { country ->
|
||||
_state.update { state ->
|
||||
state.update { state ->
|
||||
state.copy(
|
||||
items = listOf(
|
||||
OnrampSettingsItemUM.Residence(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.onramp.GetOnrampStatusUseCase
|
||||
import com.tangem.domain.onramp.GetOnrampTransactionUseCase
|
||||
import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase
|
||||
|
|
@ -21,7 +22,6 @@ import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
|||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
|
||||
import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.onramp.component.OnrampSuccessComponent
|
||||
import com.tangem.features.onramp.impl.R
|
||||
|
|
@ -32,6 +32,7 @@ import com.tangem.features.onramp.utils.sendOnrampErrorEvent
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.PeriodicTask
|
||||
import com.tangem.utils.coroutines.SingleTaskScheduler
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
|
@ -66,7 +67,7 @@ internal class OnrampSuccessComponentModel @Inject constructor(
|
|||
|
||||
private var userWallet: UserWallet by Delegates.notNull()
|
||||
private var cryptoCurrency: CryptoCurrency by Delegates.notNull()
|
||||
private var expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>()
|
||||
private val expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>()
|
||||
|
||||
init {
|
||||
loadData()
|
||||
|
|
@ -119,7 +120,7 @@ internal class OnrampSuccessComponentModel @Inject constructor(
|
|||
isDelayFirst = false,
|
||||
delay = EXPRESS_STATUS_UPDATE_DELAY,
|
||||
task = {
|
||||
runCatching {
|
||||
runSuspendCatching {
|
||||
loadTransactionStatus(transaction)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
|
|||
import com.tangem.features.onramp.utils.UpdateSearchBarCallbacksTransformer
|
||||
import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
|
@ -68,7 +69,7 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
|
||||
val state: StateFlow<TokenListUM> = tokenListUMController.state
|
||||
|
||||
private var params: AvailableSwapPairsComponent.Params = paramsContainer.require()
|
||||
private val params: AvailableSwapPairsComponent.Params = paramsContainer.require()
|
||||
private val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
|
||||
|
||||
private val tokenListFlow = getTokenListUseCaseFlow()
|
||||
|
|
@ -140,9 +141,9 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
availablePairs = pairs,
|
||||
)
|
||||
},
|
||||
ifError = {
|
||||
ifError = { throwable ->
|
||||
handleErrorState(
|
||||
cause = it,
|
||||
cause = throwable,
|
||||
networkInfo = params.selectedStatus.value?.toLeastTokenInfo(),
|
||||
currencies = currencies,
|
||||
)
|
||||
|
|
@ -184,9 +185,9 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
isAccountsMode = isAccountsMode,
|
||||
)
|
||||
},
|
||||
ifError = {
|
||||
ifError = { throwable ->
|
||||
handleErrorStateV2(
|
||||
cause = it,
|
||||
cause = throwable,
|
||||
networkInfo = params.selectedStatus.value?.toLeastTokenInfo(),
|
||||
accountList = accountList,
|
||||
)
|
||||
|
|
@ -218,7 +219,7 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
isBalanceHidden = isBalanceHidden,
|
||||
unavailableTokensHeaderReference = resourceReference(
|
||||
id = R.string.tokens_list_unavailable_to_swap_header,
|
||||
wrappedList(selectedStatus?.currency?.name?.capitalize() ?: ""),
|
||||
wrappedList(selectedStatus?.currency?.name?.capitalize().orEmpty()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -242,7 +243,7 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
isBalanceHidden = isBalanceHidden,
|
||||
unavailableTokensHeaderReference = resourceReference(
|
||||
id = R.string.tokens_list_unavailable_to_swap_header,
|
||||
wrappedList(selectedStatus?.currency?.name?.capitalize() ?: ""),
|
||||
wrappedList(selectedStatus?.currency?.name?.capitalize().orEmpty()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -369,7 +370,7 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun updateAvailablePairs(networkInfo: LeastTokenInfo, statuses: List<CryptoCurrencyStatus>) {
|
||||
runCatching {
|
||||
runSuspendCatching {
|
||||
availablePairsByNetworkFlow.update(networkInfo = networkInfo, state = lceLoading())
|
||||
|
||||
getAvailablePairsUseCase(
|
||||
|
|
@ -390,9 +391,9 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
networkInfo: LeastTokenInfo,
|
||||
state: AvailablePairsState,
|
||||
) {
|
||||
update {
|
||||
it.toMutableMap().apply {
|
||||
put(networkInfo, state)
|
||||
update { map ->
|
||||
map.toMutableMap().apply {
|
||||
this[networkInfo] = state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -433,9 +434,9 @@ internal class AvailableSwapPairsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun List<CryptoCurrencyStatus>.filterByQuery(query: String): List<CryptoCurrencyStatus> {
|
||||
return filter {
|
||||
it.currency.name.contains(other = query, ignoreCase = true) ||
|
||||
it.currency.symbol.contains(other = query, ignoreCase = true)
|
||||
return filter { status ->
|
||||
status.currency.name.contains(other = query, ignoreCase = true) ||
|
||||
status.currency.symbol.contains(other = query, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,24 +15,23 @@ import javax.inject.Inject
|
|||
*/
|
||||
internal class SwapSelectTokensController @Inject constructor() {
|
||||
|
||||
val state: StateFlow<SwapSelectTokensUM> get() = _state
|
||||
|
||||
private val _state: MutableStateFlow<SwapSelectTokensUM> = MutableStateFlow(
|
||||
value = SwapSelectTokensUM(
|
||||
onBackClick = {},
|
||||
exchangeFrom = createEmptyExchangeFrom(),
|
||||
exchangeTo = createEmptyExchangeTo(),
|
||||
isBalanceHidden = false,
|
||||
),
|
||||
)
|
||||
val state: StateFlow<SwapSelectTokensUM>
|
||||
field = MutableStateFlow(
|
||||
value = SwapSelectTokensUM(
|
||||
onBackClick = {},
|
||||
exchangeFrom = createEmptyExchangeFrom(),
|
||||
exchangeTo = createEmptyExchangeTo(),
|
||||
isBalanceHidden = false,
|
||||
),
|
||||
)
|
||||
|
||||
fun update(transform: (SwapSelectTokensUM) -> SwapSelectTokensUM) {
|
||||
Timber.d("Applying non-name transformation")
|
||||
_state.update(transform)
|
||||
state.update(transform)
|
||||
}
|
||||
|
||||
fun update(transformer: SwapSelectTokensUMTransformer) {
|
||||
Timber.d("Applying ${transformer::class.simpleName}")
|
||||
_state.update(transformer::transform)
|
||||
Timber.d("Applying ${transformer::class.simpleName ?: "null"}")
|
||||
state.update(transformer::transform)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@ import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
|||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.features.onramp.component.SwapSelectTokensComponent
|
||||
import com.tangem.features.onramp.swap.entity.SwapSelectTokensController
|
||||
|
|
@ -43,15 +42,14 @@ internal class SwapSelectTokensModel @Inject constructor(
|
|||
|
||||
val state: StateFlow<SwapSelectTokensUM> = controller.state
|
||||
|
||||
val fromCurrencyStatus: StateFlow<CryptoCurrencyStatus?> get() = _fromCurrencyStatus
|
||||
val fromCurrencyStatus: StateFlow<CryptoCurrencyStatus?>
|
||||
field = MutableStateFlow<CryptoCurrencyStatus?>(value = null)
|
||||
|
||||
private val _fromCurrencyStatus = MutableStateFlow<CryptoCurrencyStatus?>(value = null)
|
||||
private val _toCurrencyStatus = MutableStateFlow<CryptoCurrencyStatus?>(value = null)
|
||||
|
||||
private val params = paramsContainer.require<SwapSelectTokensComponent.Params>()
|
||||
|
||||
private var isAccountsMode: Boolean = false
|
||||
private var account: Account.CryptoPortfolio? = null
|
||||
|
||||
init {
|
||||
controller.update { it.copy(onBackClick = ::onBackClick) }
|
||||
|
|
@ -71,7 +69,7 @@ internal class SwapSelectTokensModel @Inject constructor(
|
|||
event = MainScreenAnalyticsEvent.SwapTokenClicked(currencySymbol = status.currency.symbol),
|
||||
)
|
||||
|
||||
_fromCurrencyStatus.value = status
|
||||
fromCurrencyStatus.value = status
|
||||
|
||||
modelScope.launch {
|
||||
controller.update(
|
||||
|
|
@ -173,7 +171,7 @@ internal class SwapSelectTokensModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onRemoveFromTokenClick() {
|
||||
val currencySymbol = requireNotNull(_fromCurrencyStatus.value?.currency?.symbol) {
|
||||
val currencySymbol = requireNotNull(fromCurrencyStatus.value?.currency?.symbol) {
|
||||
"Token was not selected"
|
||||
}
|
||||
|
||||
|
|
@ -185,7 +183,7 @@ internal class SwapSelectTokensModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun removeSelectedFromToken() {
|
||||
_fromCurrencyStatus.value = null
|
||||
fromCurrencyStatus.value = null
|
||||
|
||||
controller.update(transformer = RemoveSelectedFromTokenTransformer)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,37 +18,36 @@ import javax.inject.Inject
|
|||
*/
|
||||
internal class TokenListUMController @Inject constructor() {
|
||||
|
||||
val state: StateFlow<TokenListUM> get() = _state
|
||||
|
||||
private val _state: MutableStateFlow<TokenListUM> = MutableStateFlow(
|
||||
value = TokenListUM(
|
||||
searchBarUM = SearchBarUM(
|
||||
placeholderText = resourceReference(id = R.string.common_search),
|
||||
query = "",
|
||||
onQueryChange = {},
|
||||
isActive = false,
|
||||
onActiveChange = {},
|
||||
val state: StateFlow<TokenListUM>
|
||||
field = MutableStateFlow(
|
||||
value = TokenListUM(
|
||||
searchBarUM = SearchBarUM(
|
||||
placeholderText = resourceReference(id = R.string.common_search),
|
||||
query = "",
|
||||
onQueryChange = {},
|
||||
isActive = false,
|
||||
onActiveChange = {},
|
||||
),
|
||||
availableItems = persistentListOf(),
|
||||
unavailableItems = persistentListOf(),
|
||||
tokensListData = TokenListUMData.EmptyList,
|
||||
isBalanceHidden = false,
|
||||
),
|
||||
availableItems = persistentListOf(),
|
||||
unavailableItems = persistentListOf(),
|
||||
tokensListData = TokenListUMData.EmptyList,
|
||||
isBalanceHidden = false,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
fun update(transform: (TokenListUM) -> TokenListUM) {
|
||||
Timber.d("Applying non-name transformation")
|
||||
_state.update(transform)
|
||||
state.update(transform)
|
||||
}
|
||||
|
||||
fun update(transformer: TokenListUMTransformer) {
|
||||
Timber.d("Applying ${transformer::class.simpleName}")
|
||||
_state.update(transformer::transform)
|
||||
Timber.d("Applying ${transformer::class.simpleName ?: "unknown"}")
|
||||
state.update(transformer::transform)
|
||||
}
|
||||
|
||||
fun update(transformer: SearchBarUMTransformer) {
|
||||
Timber.d("Applying ${transformer::class.simpleName}")
|
||||
_state.update { prevState ->
|
||||
Timber.d("Applying ${transformer::class.simpleName ?: "unknown"}")
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
searchBarUM = transformer.transform(prevState.searchBarUM),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
|||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMData
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class SetNothingToFoundStateTransformer(
|
||||
private val isBalanceHidden: Boolean,
|
||||
|
|
@ -17,17 +16,15 @@ internal class SetNothingToFoundStateTransformer(
|
|||
|
||||
override fun transform(prevState: TokenListUM): TokenListUM {
|
||||
return prevState.copy(
|
||||
availableItems = buildList {
|
||||
availableItems = persistentListOf(
|
||||
createGroupTitle(
|
||||
textReference = resourceReference(id = R.string.exchange_tokens_available_tokens_header),
|
||||
)
|
||||
.let(::add)
|
||||
|
||||
),
|
||||
TokensListItemUM.Text(
|
||||
id = emptySearchMessageReference.hashCode(),
|
||||
text = emptySearchMessageReference,
|
||||
).let(::add)
|
||||
}.toImmutableList(),
|
||||
),
|
||||
),
|
||||
unavailableItems = persistentListOf(),
|
||||
tokensListData = TokenListUMData.EmptyList,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
|||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMData
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class SetNothingToFoundStateTransformerV2(
|
||||
private val isBalanceHidden: Boolean,
|
||||
|
|
@ -17,12 +16,14 @@ internal class SetNothingToFoundStateTransformerV2(
|
|||
return prevState.copy(
|
||||
availableItems = persistentListOf(),
|
||||
unavailableItems = persistentListOf(),
|
||||
tokensListData = TokenListUMData.TokenList(tokensList = buildList {
|
||||
TokensListItemUM.Text(
|
||||
id = emptySearchMessageReference.hashCode(),
|
||||
text = emptySearchMessageReference,
|
||||
).let(::add)
|
||||
}.toImmutableList()),
|
||||
tokensListData = TokenListUMData.TokenList(
|
||||
tokensList = persistentListOf(
|
||||
TokensListItemUM.Text(
|
||||
id = emptySearchMessageReference.hashCode(),
|
||||
text = emptySearchMessageReference,
|
||||
),
|
||||
),
|
||||
),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ internal object OnrampTokenItemStateConverterFactory {
|
|||
): TokenItemStateConverter {
|
||||
return TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
subtitleStateProvider = {
|
||||
subtitleStateProvider = { status ->
|
||||
createSubtitleState(
|
||||
status = it,
|
||||
status = status,
|
||||
isAvailable = true,
|
||||
text = stringReference(value = it.currency.symbol),
|
||||
text = stringReference(value = status.currency.symbol),
|
||||
)
|
||||
},
|
||||
subtitle2StateProvider = ::createSubtitle2State,
|
||||
|
|
@ -41,16 +41,16 @@ internal object OnrampTokenItemStateConverterFactory {
|
|||
return TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
iconStateProvider = { CryptoCurrencyToIconStateConverter(isAvailable = false).convert(it) },
|
||||
titleStateProvider = {
|
||||
titleStateProvider = { status ->
|
||||
TokenItemState.TitleState.Content(
|
||||
text = stringReference(value = it.currency.name),
|
||||
text = stringReference(value = status.currency.name),
|
||||
isAvailable = false,
|
||||
)
|
||||
},
|
||||
subtitleStateProvider = {
|
||||
subtitleStateProvider = { status ->
|
||||
createSubtitleState(
|
||||
status = it,
|
||||
text = stringReference(value = it.currency.symbol),
|
||||
status = status,
|
||||
text = stringReference(value = status.currency.symbol),
|
||||
isAvailable = false,
|
||||
)
|
||||
},
|
||||
|
|
@ -68,15 +68,15 @@ internal object OnrampTokenItemStateConverterFactory {
|
|||
return TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
iconStateProvider = { CryptoCurrencyToIconStateConverter(isAvailable = false).convert(it) },
|
||||
titleStateProvider = {
|
||||
titleStateProvider = { status ->
|
||||
TokenItemState.TitleState.Content(
|
||||
text = stringReference(value = it.currency.name),
|
||||
text = stringReference(value = status.currency.name),
|
||||
isAvailable = false,
|
||||
)
|
||||
},
|
||||
subtitleStateProvider = {
|
||||
subtitleStateProvider = { status ->
|
||||
createSubtitleState(
|
||||
status = it,
|
||||
status = status,
|
||||
isAvailable = false,
|
||||
text = unavailableErrorText,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ fun List<TokensListItemUM>.addHeader(textReference: TextReference): ImmutableLis
|
|||
|
||||
return buildList {
|
||||
if (items.isNotEmpty()) {
|
||||
createGroupTitle(textReference = textReference).let(::add)
|
||||
add(createGroupTitle(textReference = textReference))
|
||||
}
|
||||
|
||||
addAll(items)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.common.ui.notifications.NotificationUM
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.components.fields.InputManager
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
|
|
@ -120,11 +121,11 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
UpdateTokenItemsTransformer(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = params.onTokenClick,
|
||||
statuses = filterByQueryTokenList.let {
|
||||
statuses = filterByQueryTokenList.let { statuses ->
|
||||
if (hasRestrictionForSell || isInsufficientBalanceForSell) {
|
||||
mapOf(false to it)
|
||||
mapOf(false to statuses)
|
||||
} else {
|
||||
it.filterByAvailability()
|
||||
statuses.filterByAvailability()
|
||||
}
|
||||
},
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
|
|
@ -226,17 +227,25 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getUnavailableTokensHeaderReference() = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> R.string.tokens_list_unavailable_to_purchase_header
|
||||
OnrampOperation.SELL -> R.string.tokens_list_unavailable_to_sell_header
|
||||
OnrampOperation.SWAP -> R.string.tokens_list_unavailable_to_swap_source_header
|
||||
}.let(::resourceReference)
|
||||
private fun getUnavailableTokensHeaderReference(): TextReference {
|
||||
val res = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> R.string.tokens_list_unavailable_to_purchase_header
|
||||
OnrampOperation.SELL -> R.string.tokens_list_unavailable_to_sell_header
|
||||
OnrampOperation.SWAP -> R.string.tokens_list_unavailable_to_swap_source_header
|
||||
}
|
||||
|
||||
private fun getEmptySearchMessageReference() = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> R.string.action_buttons_buy_empty_search_message
|
||||
OnrampOperation.SELL -> R.string.action_buttons_sell_empty_search_message
|
||||
OnrampOperation.SWAP -> R.string.action_buttons_swap_empty_search_message
|
||||
}.let(::resourceReference)
|
||||
return resourceReference(res)
|
||||
}
|
||||
|
||||
private fun getEmptySearchMessageReference(): TextReference {
|
||||
val res = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> R.string.action_buttons_buy_empty_search_message
|
||||
OnrampOperation.SELL -> R.string.action_buttons_sell_empty_search_message
|
||||
OnrampOperation.SWAP -> R.string.action_buttons_swap_empty_search_message
|
||||
}
|
||||
|
||||
return resourceReference(res)
|
||||
}
|
||||
|
||||
private fun updateTokenListUM(transformer: TokenListUMTransformer) {
|
||||
modelScope.launch {
|
||||
|
|
@ -297,9 +306,9 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
}.filter { (_, value) -> value.isNotEmpty() }
|
||||
|
||||
private fun List<CryptoCurrencyStatus>.filterByQuery(query: String): List<CryptoCurrencyStatus> {
|
||||
return filter {
|
||||
it.currency.name.contains(other = query, ignoreCase = true) ||
|
||||
it.currency.symbol.contains(other = query, ignoreCase = true)
|
||||
return filter { status ->
|
||||
status.currency.name.contains(other = query, ignoreCase = true) ||
|
||||
status.currency.symbol.contains(other = query, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue