diff --git a/detekt_baseline_report.txt b/detekt_baseline_report.txt index ce40f10ef0..3a9f9a9044 100644 --- a/detekt_baseline_report.txt +++ b/detekt_baseline_report.txt @@ -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 diff --git a/features/onramp/impl/detekt-baseline-debug.xml b/features/onramp/impl/detekt-baseline-debug.xml index ea9c735b45..4cc1802ed8 100644 --- a/features/onramp/impl/detekt-baseline-debug.xml +++ b/features/onramp/impl/detekt-baseline-debug.xml @@ -2,41 +2,6 @@ - BooleanPropertyNaming:ConfirmResidencyComponent.kt$ConfirmResidencyComponent.Params$val launchSepa: Boolean - BooleanPropertyNaming:DefaultHotCryptoComponent.kt$DefaultHotCryptoComponent$val scrollableContent = when (stack.active.configuration) { OnrampAddTokenRoute.PortfolioSelector -> false OnrampAddTokenRoute.AddToken, OnrampAddTokenRoute.Empty, -> true } - BooleanPropertyNaming:OnrampAddTokenComponent.kt$OnrampAddTokenComponent.AddHotCryptoData$val availableMorePortfolio: Boolean - BooleanPropertyNaming:OnrampMainComponent.kt$OnrampMainComponent.Params$val launchSepa: Boolean - BooleanPropertyNaming:OnrampMainComponentUM.kt$BuyButtonConfig$val enabled: Boolean - BooleanPropertyNaming:ProviderListPaymentMethodUM.kt$ProviderListPaymentMethodUM$val enabled: Boolean - BooleanPropertyNaming:SelectProviderModel.kt$SelectProviderModel$val allErrorProviders = providers.all { it is OnrampProviderWithQuote.Unavailable.NotSupportedPaymentMethod } - MaxChainedCallsOnSameLine:OnrampMainComponentModel.kt$OnrampMainComponentModel$content.amountBlockState.amountFieldModel.fiatAmount.value.isNullOrZero() - MaxChainedCallsOnSameLine:OnrampV2MainComponentModel.kt$OnrampV2MainComponentModel$content.amountBlockState.amountFieldModel.fiatAmount.value.isNullOrZero() - MultilineLambdaItParameter:AvailableSwapPairsModel.kt$AvailableSwapPairsModel${ handleErrorState( cause = it, networkInfo = params.selectedStatus.value?.toLeastTokenInfo(), currencies = currencies, ) } - MultilineLambdaItParameter:AvailableSwapPairsModel.kt$AvailableSwapPairsModel${ handleErrorStateV2( cause = it, networkInfo = params.selectedStatus.value?.toLeastTokenInfo(), accountList = accountList, ) } - MultilineLambdaItParameter:AvailableSwapPairsModel.kt$AvailableSwapPairsModel${ it.currency.name.contains(other = query, ignoreCase = true) || it.currency.symbol.contains(other = query, ignoreCase = true) } - MultilineLambdaItParameter:AvailableSwapPairsModel.kt$AvailableSwapPairsModel${ it.toMutableMap().apply { put(networkInfo, state) } } - MultilineLambdaItParameter:DefaultOnrampComponent.kt$DefaultOnrampComponent${ navigation.push( OnrampChild.RedirectPage( quote = it, cryptoCurrency = params.cryptoCurrency, ), ) } - MultilineLambdaItParameter:HotCryptoModel.kt$HotCryptoModel${ Timber.e(it) closeNavigationFlow() } - MultilineLambdaItParameter:HotCryptoModel.kt$HotCryptoModel${ bottomSheetNavigation.dismiss() params.onTokenClick(it) } - 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, ) } - MultilineLambdaItParameter:OnrampAddToPortfolioModel.kt$OnrampAddToPortfolioModel${ Timber.e("Failed to derive public keys: $it") changeAddButtonProgressStatus(isProgress = false) } - MultilineLambdaItParameter:OnrampAddTokenModel.kt$OnrampAddTokenModel${ processError(error = it) uiState.value = um.toggleProgress(false) return@launch } - MultilineLambdaItParameter:OnrampMainComponentModel.kt$OnrampMainComponentModel${ (it as? OnrampMainComponentUM.Content)?.copy( errorNotification = null, providerBlockState = OnrampProviderBlockUM.Loading, amountBlockState = it.amountBlockState.copy(secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading), ) ?: it } - MultilineLambdaItParameter:OnrampMainComponentModel.kt$OnrampMainComponentModel${ if (it is OnrampMainComponentUM.InitialLoading) { stateFactory.getReadyState(country.defaultCurrency) } else { amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency) } } - MultilineLambdaItParameter:OnrampMainComponentModel.kt$OnrampMainComponentModel${ it.provider.id == providerState?.providerId && it.paymentMethod.id == providerState.paymentMethod.id } - MultilineLambdaItParameter:OnrampOffersContent.kt${ SpacerH(12.dp) SecondaryButton( modifier = Modifier.fillMaxWidth(), text = it.title.resolveReference(), onClick = it.onClick, ) } - 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, ) } - 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) } } - MultilineLambdaItParameter:OnrampTokenItemStateConverterFactory.kt$OnrampTokenItemStateConverterFactory${ TokenItemState.TitleState.Content( text = stringReference(value = it.currency.name), isAvailable = false, ) } - MultilineLambdaItParameter:OnrampTokenItemStateConverterFactory.kt$OnrampTokenItemStateConverterFactory${ createSubtitleState( status = it, isAvailable = false, text = unavailableErrorText, ) } - MultilineLambdaItParameter:OnrampTokenItemStateConverterFactory.kt$OnrampTokenItemStateConverterFactory${ createSubtitleState( status = it, isAvailable = true, text = stringReference(value = it.currency.symbol), ) } - MultilineLambdaItParameter:OnrampTokenItemStateConverterFactory.kt$OnrampTokenItemStateConverterFactory${ createSubtitleState( status = it, text = stringReference(value = it.currency.symbol), isAvailable = false, ) } - MultilineLambdaItParameter:OnrampTokenListModel.kt$OnrampTokenListModel${ if (hasRestrictionForSell || isInsufficientBalanceForSell) { mapOf(false to it) } else { it.filterByAvailability() } } - MultilineLambdaItParameter:OnrampTokenListModel.kt$OnrampTokenListModel${ it.currency.name.contains(other = query, ignoreCase = true) || it.currency.symbol.contains(other = query, ignoreCase = true) } - MultilineLambdaItParameter:OnrampV2MainComponentModel.kt$OnrampV2MainComponentModel${ (it as? OnrampV2MainComponentUM.Content)?.copy( errorNotification = null, offersBlockState = OnrampOffersBlockUM.Loading, amountBlockState = it.amountBlockState.copy(secondaryFieldModel = OnrampSecondaryFieldErrorUM.Empty), ) ?: it } - MultilineLambdaItParameter:OnrampV2MainComponentModel.kt$OnrampV2MainComponentModel${ when (it) { is OnrampV2MainComponentUM.Content -> { amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency) } is OnrampV2MainComponentUM.InitialLoading -> { stateFactory.getReadyState(country.defaultCurrency) } } } - 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 } } - NoNameShadowing:OnrampAddTokenUiBuilder.kt$OnrampAddTokenUiBuilder$tokenToAdd NonBooleanPropertyPrefixedWithIs:AvailableSwapPairsModel.kt$AvailableSwapPairsModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase NonBooleanPropertyPrefixedWithIs:HotCryptoModel.kt$HotCryptoModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase NonBooleanPropertyPrefixedWithIs:OnrampAddTokenUiBuilder.kt$OnrampAddTokenUiBuilder$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase @@ -44,52 +9,5 @@ NonBooleanPropertyPrefixedWithIs:OnrampOperationModel.kt$OnrampOperationModel$private val isDemoCardUseCase: IsDemoCardUseCase NonBooleanPropertyPrefixedWithIs:OnrampTokenListModel.kt$OnrampTokenListModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase NonBooleanPropertyPrefixedWithIs:SwapSelectTokensModel.kt$SwapSelectTokensModel$private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase - NullableBooleanCheck:HotCryptoModel.kt$HotCryptoModel$hotCryptoPortfolioData.wallet.accounts .find { it.account.accountId == accountStatus.accountId } ?.addedHotCrypto ?.none { it.currency.id.rawCurrencyId == hotCrypto.cryptoCurrency.id.rawCurrencyId } ?: false - NullableToStringCall:SwapSelectTokensController.kt$SwapSelectTokensController$${transformer::class.simpleName} - NullableToStringCall:TokenListUMController.kt$TokenListUMController$${transformer::class.simpleName} - PropertyUsedBeforeDeclaration:AllOffersModel.kt$AllOffersModel$state - PropertyUsedBeforeDeclaration:CountryListUMController.kt$CountryListUMController$_state - PropertyUsedBeforeDeclaration:CurrencyListController.kt$CurrencyListController$_state - PropertyUsedBeforeDeclaration:HotCryptoModel.kt$HotCryptoModel$_state - PropertyUsedBeforeDeclaration:OnrampAddToPortfolioModel.kt$OnrampAddToPortfolioModel$_state - PropertyUsedBeforeDeclaration:OnrampMainComponentModel.kt$OnrampMainComponentModel$_state - PropertyUsedBeforeDeclaration:OnrampMainComponentModel.kt$OnrampMainComponentModel$userCountry - PropertyUsedBeforeDeclaration:OnrampOperationModel.kt$OnrampOperationModel$_state - PropertyUsedBeforeDeclaration:OnrampSelectCountryModel.kt$OnrampSelectCountryModel$controller - PropertyUsedBeforeDeclaration:OnrampSelectCurrencyModel.kt$OnrampSelectCurrencyModel$controller - PropertyUsedBeforeDeclaration:OnrampSettingsModel.kt$OnrampSettingsModel$_state - PropertyUsedBeforeDeclaration:OnrampV2MainComponentModel.kt$OnrampV2MainComponentModel$_state - PropertyUsedBeforeDeclaration:SelectProviderModel.kt$SelectProviderModel$_state - PropertyUsedBeforeDeclaration:SwapSelectTokensController.kt$SwapSelectTokensController$_state - PropertyUsedBeforeDeclaration:SwapSelectTokensModel.kt$SwapSelectTokensModel$_fromCurrencyStatus - PropertyUsedBeforeDeclaration:TokenListUMController.kt$TokenListUMController$_state - RedundantSuspendModifier:OnrampSelectCountryModel.kt$OnrampSelectCountryModel$suspend - ReusedModifierInstance:DefaultOnrampComponent.kt$DefaultOnrampComponent$Content(modifier = modifier) - SuspendFunSwallowedCancellation:AvailableSwapPairsModel.kt$AvailableSwapPairsModel$runCatching - SuspendFunSwallowedCancellation:OnrampMainComponentModel.kt$OnrampMainComponentModel$runCatching - SuspendFunSwallowedCancellation:OnrampSuccessComponentModel.kt$OnrampSuccessComponentModel$runCatching - SuspendFunSwallowedCancellation:OnrampV2MainComponentModel.kt$OnrampV2MainComponentModel$runCatching - 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) } } } - UnnecessaryLet:AllOffersModel.kt$AllOffersModel$let { analyticsEventHandler::send } - UnnecessaryLet:HotTokenItemStateConverter.kt$HotTokenItemStateConverter$let { onItemClick -> { onItemClick(it, value) } } - UnnecessaryLet:OnrampButtonComponent.kt$let { state.onLinkClick(privacyPolicyLink) } - UnnecessaryLet:OnrampButtonComponent.kt$let { state.onLinkClick(termsOfUseLink) } - UnnecessaryLet:OnrampTokenListModel.kt$OnrampTokenListModel$let(::resourceReference) - UnnecessaryLet:OnrampTokenListUMExt.kt$let(::add) - UnnecessaryLet:OnrampV2MainComponentModel.kt$OnrampV2MainComponentModel$let(analyticsEventHandler::send) - UnnecessaryLet:SetNothingToFoundStateTransformer.kt$SetNothingToFoundStateTransformer$let(::add) - UnnecessaryLet:SetNothingToFoundStateTransformerV2.kt$SetNothingToFoundStateTransformerV2$let(::add) - UnsafeCallOnNullableType:DefaultHotCryptoComponent.kt$DefaultHotCryptoComponent$addTokenComponent!! - UnsafeCallOnNullableType:DefaultHotCryptoComponent.kt$DefaultHotCryptoComponent$model.portfolioFetcher!! - UnsafeCallOnNullableType:DefaultHotCryptoComponent.kt$DefaultHotCryptoComponent$portfolioSelectorComponent!! - UseEmptyCounterpart:HotCryptoPortfolioDataLoader.kt$HotCryptoPortfolioDataLoader$listOf() - UseEmptyCounterpart:SelectProviderPreviewData.kt$SelectProviderPreviewData$listOf() - UseOrEmpty:AvailableSwapPairsModel.kt$AvailableSwapPairsModel$selectedStatus?.currency?.name?.capitalize() ?: "" - UseOrEmpty:HotCryptoPortfolioDataLoader.kt$HotCryptoPortfolioDataLoader$getAccountCurrencyStatusUseCase .invokeSync(userWalletId, hotCryptoCurrencies) .getOrNull() ?: emptyMap() - UseOrEmpty:HotCryptoPortfolioDataLoader.kt$HotCryptoPortfolioDataLoader$mapOfAddedCurrencies[account.account] ?: listOf() - UselessCallOnNotNull:OnrampAddToPortfolioModel.kt$OnrampAddToPortfolioModel$listOfNotNull(params.cryptoCurrency) - VarCouldBeVal:AvailableSwapPairsModel.kt$AvailableSwapPairsModel$private var params: AvailableSwapPairsComponent.Params = paramsContainer.require() - VarCouldBeVal:OnrampSuccessComponentModel.kt$OnrampSuccessComponentModel$private var expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>() - VarCouldBeVal:SwapSelectTokensModel.kt$SwapSelectTokensModel$private var account: Account.CryptoPortfolio? = null diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/model/AllOffersModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/model/AllOffersModel.kt index 12b43915ad..cf2c6af946 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/model/AllOffersModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/model/AllOffersModel.kt @@ -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 + field = MutableStateFlow(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 = MutableStateFlow(AllOffersStateUM.Loading) - val state: StateFlow = _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) } } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt index 6942e44992..1957e8b15e 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt @@ -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, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt index 724ec2e7a4..989bebc335 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt @@ -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) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/DefaultHotCryptoComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/DefaultHotCryptoComponent.kt index 852c1ae304..feff4ba69b 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/DefaultHotCryptoComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/DefaultHotCryptoComponent.kt @@ -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 } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/converter/HotTokenItemStateConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/converter/HotTokenItemStateConverter.kt index 4f55af238c..a9e87d2738 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/converter/HotTokenItemStateConverter.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/converter/HotTokenItemStateConverter.kt @@ -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, ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt index 3572d99f56..75812c5cef 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt @@ -83,8 +83,8 @@ internal class HotCryptoModel @Inject constructor( onBufferOverflow = BufferOverflow.DROP_OLDEST, ) - val state: StateFlow get() = _state - private val _state = MutableStateFlow(value = HotCryptoUM(items = persistentListOf())) + val state: StateFlow + 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 } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoPortfolioDataLoader.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoPortfolioDataLoader.kt index b47d4f39ee..961019b158 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoPortfolioDataLoader.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoPortfolioDataLoader.kt @@ -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, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/OnrampAddTokenComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/OnrampAddTokenComponent.kt index 2e79cd6137..a965f4639b 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/OnrampAddTokenComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/OnrampAddTokenComponent.kt @@ -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 { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddTokenUiBuilder.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddTokenUiBuilder.kt index 48def24fab..edd05f63b2 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddTokenUiBuilder.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddTokenUiBuilder.kt @@ -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, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddToPortfolioModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddToPortfolioModel.kt index e11a0780e7..66fa4f5693 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddToPortfolioModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddToPortfolioModel.kt @@ -40,8 +40,8 @@ internal class OnrampAddToPortfolioModel @Inject constructor( private val params: OnrampAddToPortfolioComponent.Params = paramsContainer.require() - val state: StateFlow get() = _state - private val _state = MutableStateFlow(value = getInitialState()) + val state: StateFlow + 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)) } } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddTokenModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddTokenModel.kt index 4d76738d34..8a895a5fb9 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddTokenModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/model/OnrampAddTokenModel.kt @@ -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 } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt index d03c60d6d0..42967bcc47 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt @@ -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() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/OnrampMainComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/OnrampMainComponent.kt index fa5cf89457..d4858314cf 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/OnrampMainComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/OnrampMainComponent.kt @@ -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 diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt index e375c15faf..72fa50f884 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt @@ -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, ) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt index 02de508683..b0a9d7773a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt @@ -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), ), diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountFieldChangeConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountFieldChangeConverter.kt index 02c3f9fa33..ef9cff01ac 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountFieldChangeConverter.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountFieldChangeConverter.kt @@ -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, ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt index 4d466907aa..b3569cec78 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt @@ -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( diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt index 6b3a4d73a6..18b31bb447 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -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 = SlotNavigation() + + private val lastUpdateState = mutableStateOf(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 + field = MutableStateFlow( + 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 = MutableStateFlow( - value = stateFactory.getInitialState( - currency = params.cryptoCurrency.name, - onClose = ::onCloseClick, - ), - ) - private val quotesTaskScheduler = SingleTaskScheduler() - val state: StateFlow get() = _state.asStateFlow() - val bottomSheetNavigation: SlotNavigation = SlotNavigation() - private val lastUpdateState = mutableStateOf(null) - private var userCountry: UserCountry? = null + private val quotesTaskScheduler = SingleTaskScheduler() 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? { val quoteToCheck = quotes.firstOrNull { it !is OnrampQuote.Error } - val bestSepaQuote = if (params.launchSepa && shouldForceChooseSepa) { + val bestSepaQuote = if (params.isLaunchSepa && shouldForceChooseSepa) { quotes.filterIsInstance() .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() } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampButtonComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampButtonComponent.kt index d63b3aa736..11e356cfef 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampButtonComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampButtonComponent.kt @@ -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) } }, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/DefaultOnrampV2MainComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/DefaultOnrampV2MainComponent.kt index 104652d0a9..4d417ad448 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/DefaultOnrampV2MainComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/DefaultOnrampV2MainComponent.kt @@ -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() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt index b73709d3c0..1fc8da3ee7 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt @@ -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 + field = MutableStateFlow( + 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 = 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() - val state: StateFlow get() = _state.asStateFlow() val bottomSheetNavigation: SlotNavigation = 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) { @@ -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 { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampFooterContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampFooterContent.kt index 979234f572..e67a1fbc85 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampFooterContent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampFooterContent.kt @@ -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) } } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampNewMainComponentContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampNewMainComponentContent.kt index 1d5565f6a7..7e9499e37d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampNewMainComponentContent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampNewMainComponentContent.kt @@ -60,10 +60,7 @@ internal fun OnrampNewMainComponentContent(state: OnrampV2MainComponentUM, modif } if (state is OnrampV2MainComponentUM.Content) { - OnrampFooterContent( - state = state, - boxScope = this, - ) + OnrampFooterContent(state = state) } } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampOffersContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampOffersContent.kt index 68b2966b21..3c75e311cc 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampOffersContent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampOffersContent.kt @@ -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, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListPaymentMethodUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListPaymentMethodUM.kt index 08d293987e..da978c53f9 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListPaymentMethodUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListPaymentMethodUM.kt @@ -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, ) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt index 126eedcb3a..7c5493ae04 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt @@ -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 get() = _state.asStateFlow() + val state: StateFlow + field = MutableStateFlow(getInitialState()) + val bottomSheetNavigation: SlotNavigation = 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.sortByRate() = sortedByDescending { - when (it) { - is OnrampProviderWithQuote.Data -> it.toAmount.value + private fun List.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 } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/previewData/SelectProviderPreviewData.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/previewData/SelectProviderPreviewData.kt index f4a23e8734..6d1a3d1c1f 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/previewData/SelectProviderPreviewData.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/previewData/SelectProviderPreviewData.kt @@ -27,7 +27,7 @@ internal object SelectProviderPreviewData { termsOfUseLink = null, privacyPolicyLink = null, ), - paymentMethods = listOf(), + paymentMethods = emptyList(), ) private val providerListUM = PaymentProviderUM( diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt index 01c26e0e76..c803a99746 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt @@ -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) } } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt index 6e28350998..1133a72cfc 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt @@ -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, ), ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt index 94114f2c2a..624267e18d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt @@ -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, ) { - val state: StateFlow get() = _state.asStateFlow() - private val _state: MutableStateFlow = - MutableStateFlow(value = CountryListUM.Loading(searchBarUM = searchBarUM, items = loadingItems)) + val state: StateFlow + field = MutableStateFlow( + value = CountryListUM.Loading(searchBarUM = searchBarUM, items = loadingItems), + ) fun update(transformer: Transformer) { - _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) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt index a30217c087..8c22aeb96a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt @@ -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 get() = controller.state - private val controller = CountryListUMController( searchBarUM = createSearchBarUM(), loadingItems = loadingItems, ) + val state: StateFlow 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(), diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt index eaad4b3365..0e4d04965a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt @@ -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>, ) { - val state: StateFlow get() = _state.asStateFlow() - private val _state: MutableStateFlow = MutableStateFlow( - value = CurrenciesListUM.Loading( - searchBarUM = currencySearchBarUM, - sections = loadingSections, - ), - ) + val state: StateFlow + field = MutableStateFlow( + value = CurrenciesListUM.Loading( + searchBarUM = currencySearchBarUM, + sections = loadingSections, + ), + ) fun update(transformer: Transformer) { - _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) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt index c7899de0de..39bac756fa 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt @@ -46,14 +46,14 @@ internal class OnrampSelectCurrencyModel @Inject constructor( paramsContainer: ParamsContainer, ) : Model() { - val state: StateFlow get() = controller.state - - private val params: SelectCurrencyComponent.Params = paramsContainer.require() private val controller = CurrencyListController( currencySearchBarUM = createSearchBarUM(), loadingSections = loadingSections, ) + val state: StateFlow get() = controller.state + private val params: SelectCurrencyComponent.Params = paramsContainer.require() + init { updateCurrenciesList() subscribeOnUpdateState() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt index 890446c68e..a8957a1d0c 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt @@ -49,12 +49,11 @@ internal class OnrampOperationModel @Inject constructor( private val rampStateManager: RampStateManager, ) : Model() { - val state: StateFlow get() = _state + val state: StateFlow + 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) } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt index 95ed639744..9fe53c12a0 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt @@ -29,11 +29,12 @@ internal class OnrampSettingsModel @Inject constructor( paramsContainer: ParamsContainer, ) : Model() { - val state: StateFlow get() = _state + val state: StateFlow + field = MutableStateFlow(getInitialState()) + val bottomSheetNavigation: SlotNavigation = SlotNavigation() private val params: OnrampSettingsComponent.Params = paramsContainer.require() - private val _state: MutableStateFlow = 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( diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt index 7aab8d8e43..ad9f3c8d31 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt @@ -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() + private val expressTxStatusTaskScheduler = SingleTaskScheduler() init { loadData() @@ -119,7 +120,7 @@ internal class OnrampSuccessComponentModel @Inject constructor( isDelayFirst = false, delay = EXPRESS_STATUS_UPDATE_DELAY, task = { - runCatching { + runSuspendCatching { loadTransactionStatus(transaction) } }, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt index e6c52a0631..d862519a45 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt @@ -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 = 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) { - 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.filterByQuery(query: String): List { - 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) } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt index f2fe285085..616f0761c4 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt @@ -15,24 +15,23 @@ import javax.inject.Inject */ internal class SwapSelectTokensController @Inject constructor() { - val state: StateFlow get() = _state - - private val _state: MutableStateFlow = MutableStateFlow( - value = SwapSelectTokensUM( - onBackClick = {}, - exchangeFrom = createEmptyExchangeFrom(), - exchangeTo = createEmptyExchangeTo(), - isBalanceHidden = false, - ), - ) + val state: StateFlow + 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) } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt index 9bda5e4153..49a9b6d86d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt @@ -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 = controller.state - val fromCurrencyStatus: StateFlow get() = _fromCurrencyStatus + val fromCurrencyStatus: StateFlow + field = MutableStateFlow(value = null) - private val _fromCurrencyStatus = MutableStateFlow(value = null) private val _toCurrencyStatus = MutableStateFlow(value = null) private val params = paramsContainer.require() 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) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt index 583697cca2..5fb7287648 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt @@ -18,37 +18,36 @@ import javax.inject.Inject */ internal class TokenListUMController @Inject constructor() { - val state: StateFlow get() = _state - - private val _state: MutableStateFlow = MutableStateFlow( - value = TokenListUM( - searchBarUM = SearchBarUM( - placeholderText = resourceReference(id = R.string.common_search), - query = "", - onQueryChange = {}, - isActive = false, - onActiveChange = {}, + val state: StateFlow + 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), ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformer.kt index fcaa9d6147..1b388e9a47 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformer.kt @@ -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, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformerV2.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformerV2.kt index dafa2ac842..ddc0a2ac1f 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformerV2.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetNothingToFoundStateTransformerV2.kt @@ -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, ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/utils/OnrampTokenItemStateConverterFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/utils/OnrampTokenItemStateConverterFactory.kt index 67f0c13e19..44bf47cd76 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/utils/OnrampTokenItemStateConverterFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/utils/OnrampTokenItemStateConverterFactory.kt @@ -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, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/utils/OnrampTokenListUMExt.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/utils/OnrampTokenListUMExt.kt index 27c3500039..e283bf38b8 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/utils/OnrampTokenListUMExt.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/utils/OnrampTokenListUMExt.kt @@ -10,7 +10,7 @@ fun List.addHeader(textReference: TextReference): ImmutableLis return buildList { if (items.isNotEmpty()) { - createGroupTitle(textReference = textReference).let(::add) + add(createGroupTitle(textReference = textReference)) } addAll(items) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index c8f65cda5a..e36eabad90 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -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.filterByQuery(query: String): List { - 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) } }