From 170774cde2ee7af55aec3468183834d2012b12f7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 9 Dec 2024 14:01:08 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../common/ui/notifications/NotificationUM.kt | 9 ++++ .../data/onramp/DefaultOnrampErrorResolver.kt | 2 + .../data/onramp/DefaultOnrampRepository.kt | 2 + .../domain/onramp/model/error/OnrampError.kt | 2 + .../onramp/model/error/OnrampPairsError.kt | 5 ++ .../onramp/main/entity/OnrampIntents.kt | 1 + .../main/entity/OnrampMainComponentUM.kt | 4 ++ .../main/entity/factory/OnrampStateFactory.kt | 54 +++++++++++++++++++ .../main/model/OnrampMainComponentModel.kt | 33 +++++++++--- .../onramp/main/ui/OnrampAmountContent.kt | 2 - .../main/ui/OnrampMainComponentContent.kt | 31 ++++++----- .../utils/OnrampErrorAnalyticsSender.kt | 1 + 12 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampPairsError.kt diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt index 4db6780142..e7354ee9b1 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt @@ -227,6 +227,15 @@ sealed class NotificationUM(val config: NotificationConfig) { wrappedList(cryptoAmount, fiatAmount), ), ) + + data class OnrampErrorNotification(val onRefresh: () -> Unit) : Warning( + title = resourceReference(R.string.common_error), + subtitle = resourceReference(R.string.common_unknown_error), + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.warning_button_refresh), + onClick = onRefresh, + ), + ) } open class Info( diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt index 2f60ba2878..09bc96ca6f 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt @@ -3,6 +3,7 @@ package com.tangem.data.onramp import com.tangem.data.onramp.converters.error.OnrampErrorConverter import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.domain.onramp.model.error.OnrampError +import com.tangem.domain.onramp.model.error.OnrampPairsError import com.tangem.domain.onramp.model.error.OnrampRedirectError import com.tangem.domain.onramp.repositories.OnrampErrorResolver @@ -17,6 +18,7 @@ internal class DefaultOnrampErrorResolver( } is OnrampRedirectError.WrongRequestId -> OnrampError.RedirectError.WrongRequestId is OnrampRedirectError.VerificationFailed -> OnrampError.RedirectError.VerificationFailed + is OnrampPairsError.PairsNotFound -> OnrampError.PairsNotFound else -> { OnrampError.DomainError(throwable.message) } diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index 05d14b1d18..32ad1d581a 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -38,6 +38,7 @@ import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.onramp.model.* import com.tangem.domain.onramp.model.cache.OnrampTransaction import com.tangem.domain.onramp.model.error.OnrampError +import com.tangem.domain.onramp.model.error.OnrampPairsError import com.tangem.domain.onramp.model.error.OnrampRedirectError import com.tangem.domain.onramp.repositories.OnrampRepository import com.tangem.domain.tokens.model.Amount @@ -341,6 +342,7 @@ internal class DefaultOnrampRepository( } private suspend fun storeOnrampPairs(pairs: List, providers: List) { + if (pairs.isEmpty() || providers.isEmpty()) throw OnrampPairsError.PairsNotFound val onrampPaymentMethods = getPaymentMethods() val onrampPairs = pairs.map { pair -> val onrampProviders = pair.providers.mapNotNull { onrampProviderDTO -> diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampError.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampError.kt index c7b4a01e27..cf1f656e93 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampError.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampError.kt @@ -29,4 +29,6 @@ sealed class OnrampError { data class DomainError( val description: String?, ) : OnrampError() + + data object PairsNotFound : OnrampError() } \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampPairsError.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampPairsError.kt new file mode 100644 index 0000000000..3e390f40b3 --- /dev/null +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampPairsError.kt @@ -0,0 +1,5 @@ +package com.tangem.domain.onramp.model.error + +sealed class OnrampPairsError : Throwable() { + data object PairsNotFound : OnrampPairsError() +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt index ef55baf857..96e54c7555 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt @@ -8,4 +8,5 @@ interface OnrampIntents { fun openCurrenciesList() fun onBuyClick(quote: OnrampProviderWithQuote.Data) fun openProviders() + fun onRefresh() } \ No newline at end of file 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 afd1b4e88e..4015d007a8 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 @@ -1,6 +1,7 @@ package com.tangem.features.onramp.main.entity import androidx.compose.runtime.Immutable +import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.combinedReference @@ -13,11 +14,13 @@ internal sealed interface OnrampMainComponentUM { val topBarConfig: OnrampMainTopBarUM val buyButtonConfig: BuyButtonConfig + val errorNotification: NotificationUM? data class InitialLoading( val currency: String, val onClose: () -> Unit, val openSettings: () -> Unit, + override val errorNotification: NotificationUM? = null, ) : OnrampMainComponentUM { override val topBarConfig: OnrampMainTopBarUM = OnrampMainTopBarUM( title = combinedReference(resourceReference(R.string.common_buy), stringReference(" $currency")), @@ -43,6 +46,7 @@ internal sealed interface OnrampMainComponentUM { data class Content( override val topBarConfig: OnrampMainTopBarUM, override val buyButtonConfig: BuyButtonConfig, + override val errorNotification: NotificationUM?, val amountBlockState: OnrampAmountBlockUM, val providerBlockState: OnrampProviderBlockUM, ) : OnrampMainComponentUM 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 7616976a84..6736beb593 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 @@ -5,12 +5,16 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import com.tangem.common.ui.amountScreen.models.AmountFieldModel +import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.onramp.model.OnrampCurrency +import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.tokens.model.Amount import com.tangem.domain.tokens.model.AmountType import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.convertToAmount +import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.main.entity.* import com.tangem.utils.Provider import java.math.BigDecimal @@ -38,9 +42,59 @@ internal class OnrampStateFactory( buyButtonConfig = state.buyButtonConfig, amountBlockState = getInitialAmountBlockState(currency), providerBlockState = OnrampProviderBlockUM.Empty, + errorNotification = null, ) } + fun getOnrampErrorState(onrampError: OnrampError): OnrampMainComponentUM { + return when (onrampError) { + OnrampError.PairsNotFound -> getNoPairsErrorState() + is OnrampError.DataError, + is OnrampError.DomainError, + -> getErrorState() + is OnrampError.AmountError.TooBigError, + is OnrampError.AmountError.TooSmallError, + OnrampError.RedirectError.VerificationFailed, + OnrampError.RedirectError.WrongRequestId, + -> currentStateProvider() // ignore error state + } + } + + private fun getNoPairsErrorState(): OnrampMainComponentUM { + val state = currentStateProvider() + val contentState = state as? OnrampMainComponentUM.Content ?: return state + + return contentState.copy( + buyButtonConfig = contentState.buyButtonConfig.copy(enabled = false), + amountBlockState = contentState.amountBlockState.copy( + amountFieldModel = contentState.amountBlockState.amountFieldModel.copy(isError = true), + secondaryFieldModel = OnrampAmountSecondaryFieldUM.Error( + error = resourceReference(R.string.onramp_no_available_providers), + ), + ), + ) + } + + private fun getErrorState(): OnrampMainComponentUM { + val state = currentStateProvider() + val endButton = state.topBarConfig.endButtonUM.copy(enabled = true) + + return when (state) { + is OnrampMainComponentUM.Content -> state.copy( + topBarConfig = state.topBarConfig.copy(endButtonUM = endButton), + buyButtonConfig = state.buyButtonConfig.copy(enabled = false), + amountBlockState = state.amountBlockState.copy( + amountFieldModel = state.amountBlockState.amountFieldModel.copy(isError = true), + ), + providerBlockState = OnrampProviderBlockUM.Empty, + errorNotification = NotificationUM.Warning.OnrampErrorNotification(onrampIntents::onRefresh), + ) + is OnrampMainComponentUM.InitialLoading -> state.copy( + errorNotification = NotificationUM.Warning.OnrampErrorNotification(onrampIntents::onRefresh), + ) + } + } + private fun getInitialAmountBlockState(currency: OnrampCurrency): OnrampAmountBlockUM { return OnrampAmountBlockUM( currencyUM = OnrampCurrencyUM( 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 006544cb2a..6026f919fe 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 @@ -116,10 +116,7 @@ internal class OnrampMainComponentModel @Inject constructor( modelScope.launch { checkOnrampAvailabilityUseCase() .onRight(::handleOnrampAvailability) - .onLeft { error -> - Timber.e(error.toString()) - sendOnrampErrorAnalytic(error) - } + .onLeft(::handleOnrampError) } } @@ -136,7 +133,7 @@ internal class OnrampMainComponentModel @Inject constructor( getOnrampCurrencyUseCase.invoke() .onEach { maybeCurrency -> maybeCurrency.fold( - ifLeft = ::sendOnrampErrorAnalytic, + ifLeft = ::handleOnrampError, ifRight = { currency -> if (currency == null) return@onEach _state.update { amountStateFactory.getUpdatedCurrencyState(currency) } @@ -157,10 +154,16 @@ internal class OnrampMainComponentModel @Inject constructor( } private suspend fun updatePairsAndQuotes() { - fetchPairsUseCase.invoke(params.cryptoCurrency).onLeft(::sendOnrampErrorAnalytic) + fetchPairsUseCase.invoke(params.cryptoCurrency).onLeft(::handleOnrampError) startLoadingQuotes() } + private fun handleOnrampError(onrampError: OnrampError) { + Timber.e(onrampError.toString()) + sendOnrampErrorAnalytic(onrampError) + _state.update { stateFactory.getOnrampErrorState(onrampError) } + } + private fun startLoadingQuotes() { quotesTaskScheduler.cancelTask() quotesTaskScheduler.scheduleTask(scope = modelScope, task = loadQuotesTask()) @@ -176,7 +179,7 @@ internal class OnrampMainComponentModel @Inject constructor( fetchQuotesUseCase.invoke( amount = content.amountBlockState.amountFieldModel.fiatAmount, cryptoCurrency = params.cryptoCurrency, - ).onLeft(::sendOnrampErrorAnalytic) + ).onLeft(::handleOnrampError) } }, onSuccess = {}, @@ -188,7 +191,7 @@ internal class OnrampMainComponentModel @Inject constructor( getOnrampQuotesUseCase.invoke() .onEach { maybeQuotes -> maybeQuotes.fold( - ifLeft = ::sendOnrampErrorAnalytic, + ifLeft = ::handleOnrampError, ifRight = { quotes -> quotes.filterIsInstance().forEach { errorState -> sendOnrampErrorAnalytic(errorState.error) @@ -253,6 +256,20 @@ internal class OnrampMainComponentModel @Inject constructor( ) } + override fun onRefresh() { + _state.update { + stateFactory.getInitialState( + currency = params.cryptoCurrency.name, + onClose = router::pop, + ) + } + quotesTaskScheduler.cancelTask() + modelScope.launch { + clearOnrampCacheUseCase.invoke() + checkResidenceCountry() + } + } + override fun onDestroy() { modelScope.launch { clearOnrampCacheUseCase.invoke() } quotesTaskScheduler.cancelTask() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt index 855297b410..02bb9aade5 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt @@ -30,7 +30,6 @@ import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.main.entity.OnrampAmountBlockUM import com.tangem.features.onramp.main.entity.OnrampAmountSecondaryFieldUM import com.tangem.features.onramp.main.entity.OnrampCurrencyUM -import kotlinx.coroutines.delay @Composable internal fun OnrampAmountContent(state: OnrampAmountBlockUM, modifier: Modifier = Modifier) { @@ -85,7 +84,6 @@ private fun OnrampAmountField(amountField: AmountFieldModel) { ) LaunchedEffect(key1 = Unit) { - delay(timeMillis = 200) requester.requestFocus() } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt index 15551bbb8d..ee9194f0a3 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt @@ -16,6 +16,7 @@ import com.tangem.core.ui.components.CircleShimmer import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.appbar.TangemTopAppBar +import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.WindowInsetsZero @@ -43,16 +44,17 @@ internal fun OnrampMainComponentContent(state: OnrampMainComponentUM, modifier: .fillMaxWidth() .wrapContentHeight() when (state) { - is OnrampMainComponentUM.InitialLoading -> InitialLoading(modifier = contentModifier) + is OnrampMainComponentUM.InitialLoading -> InitialLoading(modifier = contentModifier, state = state) is OnrampMainComponentUM.Content -> Content(modifier = contentModifier, state = state) } }, floatingActionButton = { - BuyButton( + PrimaryButton( modifier = Modifier .navigationBarsPadding() .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), + text = stringResource(id = R.string.common_buy), onClick = state.buyButtonConfig.onClick, enabled = state.buyButtonConfig.enabled, ) @@ -62,9 +64,21 @@ internal fun OnrampMainComponentContent(state: OnrampMainComponentUM, modifier: } @Composable -private fun InitialLoading(modifier: Modifier = Modifier) { +private fun InitialLoading(state: OnrampMainComponentUM.InitialLoading, modifier: Modifier = Modifier) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + ) { + OnrampAmountContentLoading() + if (state.errorNotification != null) Notification(config = state.errorNotification.config) + } +} + +@Composable +private fun OnrampAmountContentLoading(modifier: Modifier = Modifier) { Column( modifier = modifier + .fillMaxWidth() .clip(shape = RoundedCornerShape(size = TangemTheme.dimens.radius16)) .background(TangemTheme.colors.background.action) .padding(vertical = TangemTheme.dimens.spacing28), @@ -97,15 +111,6 @@ private fun Content(state: OnrampMainComponentUM.Content, modifier: Modifier = M ) { OnrampAmountContent(state = state.amountBlockState) OnrampProviderContent(state = state.providerBlockState, modifier = Modifier.fillMaxWidth()) + if (state.errorNotification != null) Notification(config = state.errorNotification.config) } -} - -@Composable -private fun BuyButton(onClick: () -> Unit, enabled: Boolean, modifier: Modifier = Modifier) { - PrimaryButton( - modifier = modifier, - text = stringResource(id = R.string.common_buy), - onClick = onClick, - enabled = enabled, - ) } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/OnrampErrorAnalyticsSender.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/OnrampErrorAnalyticsSender.kt index 275cd02a25..5ca1b735b6 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/OnrampErrorAnalyticsSender.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/OnrampErrorAnalyticsSender.kt @@ -28,5 +28,6 @@ internal fun AnalyticsEventHandler.sendOnrampErrorEvent( is OnrampError.AmountError.TooSmallError, OnrampError.RedirectError.VerificationFailed, OnrampError.RedirectError.WrongRequestId, + OnrampError.PairsNotFound, -> { /* no-op */ } } \ No newline at end of file