diff --git a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt index c2b9cb9ac8..b5f053bc34 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt @@ -140,9 +140,10 @@ internal object OnrampDomainModule { @Singleton fun provideGetOnrampPaymentMethodsUseCase( onrampRepository: OnrampRepository, + settingsRepository: SettingsRepository, onrampErrorResolver: OnrampErrorResolver, ): GetOnrampPaymentMethodsUseCase { - return GetOnrampPaymentMethodsUseCase(onrampRepository, onrampErrorResolver) + return GetOnrampPaymentMethodsUseCase(onrampRepository, settingsRepository, onrampErrorResolver) } @Provides 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 8b427f58f6..25a90ba084 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 @@ -343,9 +343,7 @@ internal class DefaultOnrampRepository( override suspend fun getAvailablePaymentMethods(): Set { val quotes = requireNotNull(quotesStore.getSyncOrNull(QUOTES_KEY)) { "Quotes must not be null" } - return quotes - .filterIsInstance() - .mapTo(hashSetOf(), OnrampQuote.Data::paymentMethod) + return quotes.mapTo(hashSetOf(), OnrampQuote::paymentMethod) } override suspend fun saveSelectedPaymentMethod(paymentMethod: OnrampPaymentMethod) { diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampPaymentMethodsUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampPaymentMethodsUseCase.kt index d709e81bbf..f4528c9e4b 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampPaymentMethodsUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampPaymentMethodsUseCase.kt @@ -5,14 +5,22 @@ import com.tangem.domain.onramp.model.OnrampPaymentMethod import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampRepository +import com.tangem.domain.settings.repositories.SettingsRepository class GetOnrampPaymentMethodsUseCase( private val repository: OnrampRepository, + private val settingsRepository: SettingsRepository, private val errorResolver: OnrampErrorResolver, ) { suspend operator fun invoke(): Either> { - return Either.catch { repository.getAvailablePaymentMethods() } - .mapLeft(errorResolver::resolve) + return Either.catch { + val isGooglePayAvailable = settingsRepository.isGooglePayAvailability() + + repository.getAvailablePaymentMethods() + .toList() + .sortedBy { it.type.getPriority(isGooglePayAvailable) } + .toSet() + }.mapLeft(errorResolver::resolve) } } \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt index 9626d02dd2..22238ab748 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt @@ -31,34 +31,23 @@ class GetOnrampProviderWithQuoteUseCase( private fun List.quoteWithProvider( provider: OnrampProvider, selectedPaymentMethod: OnrampPaymentMethod, - ): OnrampProviderWithQuote? { - val quoteData = this.filterIsInstance() - val matchedPaymentMethodQuote = quoteData.firstOrNull { it.paymentMethod == selectedPaymentMethod } - val paymentMethodNotSupported = quoteData.filterNot { it.paymentMethod == selectedPaymentMethod } - val amountError = - filterIsInstance().firstOrNull { it.paymentMethod == selectedPaymentMethod } - return when { - matchedPaymentMethodQuote != null -> { - OnrampProviderWithQuote.Data( - provider = matchedPaymentMethodQuote.provider, - paymentMethod = matchedPaymentMethodQuote.paymentMethod, - toAmount = matchedPaymentMethodQuote.toAmount, - fromAmount = matchedPaymentMethodQuote.fromAmount, - ) - } - paymentMethodNotSupported.isNotEmpty() -> { - Unavailable.NotSupportedPaymentMethod( - provider = provider, - availablePaymentMethods = paymentMethodNotSupported.map(OnrampQuote.Data::paymentMethod), - ) - } - amountError != null -> { - Unavailable.Error( - amountError.provider, - amountError, - ) - } - else -> null + ): OnrampProviderWithQuote { + val matchedQuote = firstOrNull { it.paymentMethod == selectedPaymentMethod } + return when (matchedQuote) { + is OnrampQuote.Data -> OnrampProviderWithQuote.Data( + provider = matchedQuote.provider, + paymentMethod = matchedQuote.paymentMethod, + toAmount = matchedQuote.toAmount, + fromAmount = matchedQuote.fromAmount, + ) + is OnrampQuote.Error -> Unavailable.Error( + provider = matchedQuote.provider, + quoteError = matchedQuote, + ) + null -> Unavailable.NotSupportedPaymentMethod( + provider = provider, + availablePaymentMethods = provider.paymentMethods, + ) } } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListBottomSheetConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListBottomSheetConfig.kt index 966e361c38..555578bb01 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListBottomSheetConfig.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListBottomSheetConfig.kt @@ -1,6 +1,7 @@ package com.tangem.features.onramp.providers.entity import com.tangem.features.onramp.paymentmethod.entity.PaymentMethodUM +import kotlinx.collections.immutable.ImmutableList import kotlinx.serialization.Serializable @Serializable @@ -9,6 +10,6 @@ internal sealed interface ProviderListBottomSheetConfig { @Serializable data class PaymentMethods( val selectedMethodId: String, - val paymentMethodsUM: List, + val paymentMethodsUM: ImmutableList, ) : ProviderListBottomSheetConfig } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListUM.kt index 6b8b8764c2..28350458da 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListUM.kt @@ -1,8 +1,16 @@ package com.tangem.features.onramp.providers.entity +import com.tangem.domain.onramp.model.OnrampPaymentMethod import kotlinx.collections.immutable.ImmutableList -internal data class ProviderListUM( - val paymentMethod: ProviderListPaymentMethodUM, +internal data class SelectPaymentAndProviderUM( + val paymentMethods: ImmutableList, + val selectedPaymentMethod: SelectProviderUM, + val isPaymentMethodClickEnabled: Boolean, + val onPaymentMethodClick: () -> Unit, +) + +internal data class SelectProviderUM( + val paymentMethod: OnrampPaymentMethod, val providers: ImmutableList, ) \ 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 b044121515..bfc01ea790 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 @@ -29,7 +29,9 @@ import com.tangem.features.onramp.utils.sendOnrampErrorEvent import com.tangem.utils.StringsSigns.MINUS import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber @@ -41,24 +43,41 @@ import javax.inject.Inject internal class SelectProviderModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, - private val getSelectedPaymentMethodsUseCase: GetOnrampPaymentMethodsUseCase, + private val getOnrampPaymentMethodsUseCase: GetOnrampPaymentMethodsUseCase, private val getOnrampSelectedPaymentMethodUseCase: GetOnrampSelectedPaymentMethodUseCase, private val getOnrampProviderWithQuoteUseCase: GetOnrampProviderWithQuoteUseCase, private val saveSelectedPaymentMethod: OnrampSaveSelectedPaymentMethod, paramsContainer: ParamsContainer, ) : Model() { - val state: StateFlow get() = _state.asStateFlow() + val state: StateFlow get() = _state.asStateFlow() val bottomSheetNavigation: SlotNavigation = SlotNavigation() private val params: SelectProviderComponent.Params = paramsContainer.require() private val _state = MutableStateFlow(getInitialState()) init { analyticsEventHandler.send(OnrampAnalyticsEvent.ProvidersScreenOpened) + getPaymentMethods() getProviders(params.selectedPaymentMethod) subscribeToPaymentMethodUpdates() } + private fun getPaymentMethods() { + modelScope.launch { + val methods = getOnrampPaymentMethodsUseCase().fold( + ifLeft = { error -> + sendOnrampErrorEvent(error) + emptySet() + }, + ifRight = { it }, + ) + _state.value = state.value.copy( + paymentMethods = methods.toPersistentList(), + isPaymentMethodClickEnabled = methods.isNotEmpty(), + ) + } + } + private fun subscribeToPaymentMethodUpdates() { getOnrampSelectedPaymentMethodUseCase.invoke() .onEach { maybePaymentMethod -> @@ -74,11 +93,11 @@ internal class SelectProviderModel @Inject constructor( modelScope.launch { getOnrampProviderWithQuoteUseCase.invoke(paymentMethod) .onRight { quotes -> - val hasQuotesData = quotes.any { it is OnrampProviderWithQuote.Data } _state.update { state -> state.copy( - paymentMethod = state.paymentMethod.copy(enabled = hasQuotesData), - providers = quotes.toProvidersListItems(), + selectedPaymentMethod = state.selectedPaymentMethod.copy( + providers = quotes.toProvidersListItems(), + ), ) } } @@ -89,58 +108,47 @@ internal class SelectProviderModel @Inject constructor( } } - private fun getInitialState(): ProviderListUM { - return ProviderListUM( - paymentMethod = ProviderListPaymentMethodUM( - id = params.selectedPaymentMethod.id, - name = params.selectedPaymentMethod.name, - imageUrl = params.selectedPaymentMethod.imageUrl, - enabled = false, - onClick = ::openPaymentMethods, + private fun getInitialState(): SelectPaymentAndProviderUM { + return SelectPaymentAndProviderUM( + paymentMethods = persistentListOf(params.selectedPaymentMethod), + isPaymentMethodClickEnabled = false, + onPaymentMethodClick = ::openPaymentMethods, + selectedPaymentMethod = SelectProviderUM( + paymentMethod = params.selectedPaymentMethod, + providers = emptyList().toImmutableList(), ), - providers = emptyList().toImmutableList(), ) } private fun openPaymentMethods() { analyticsEventHandler.send(OnrampAnalyticsEvent.PaymentMethodsScreenOpened) - modelScope.launch { - val methods = getSelectedPaymentMethodsUseCase().fold( - ifLeft = { error -> - sendOnrampErrorEvent(error) - emptySet() - }, - ifRight = { it }, - ) - bottomSheetNavigation.activate( - ProviderListBottomSheetConfig.PaymentMethods( - selectedMethodId = _state.value.paymentMethod.id, - paymentMethodsUM = methods.toPaymentUMList(), - ), - ) - } + bottomSheetNavigation.activate( + ProviderListBottomSheetConfig.PaymentMethods( + selectedMethodId = state.value.selectedPaymentMethod.paymentMethod.id, + paymentMethodsUM = state.value.paymentMethods.toPaymentUMList(), + ), + ) } - private fun Set.toPaymentUMList(): List = map { method -> + private fun ImmutableList.toPaymentUMList(): ImmutableList = map { method -> PaymentMethodUM( id = method.id, imageUrl = method.imageUrl, name = method.name, onSelect = { onPaymentMethodSelected(method) }, ) - } + }.toPersistentList() private fun onPaymentMethodSelected(paymentMethod: OnrampPaymentMethod) { analyticsEventHandler.send(OnrampAnalyticsEvent.OnPaymentMethodChosen(paymentMethod = paymentMethod.name)) modelScope.launch { saveSelectedPaymentMethod.invoke(paymentMethod) _state.update { state -> - val paymentMethodUM = state.paymentMethod.copy( - id = paymentMethod.id, - name = paymentMethod.name, - imageUrl = paymentMethod.imageUrl, + state.copy( + selectedPaymentMethod = state.selectedPaymentMethod.copy( + paymentMethod = paymentMethod, + ), ) - state.copy(paymentMethod = paymentMethodUM) } bottomSheetNavigation.dismiss() } @@ -237,7 +245,7 @@ internal class SelectProviderModel @Inject constructor( } private fun sendOnrampErrorEvent(error: OnrampError) { - val selectedProvider = state.value.providers.firstOrNull { + val selectedProvider = state.value.selectedPaymentMethod.providers.firstOrNull { it.providerId == params.selectedProviderId } analyticsEventHandler.sendOnrampErrorEvent( 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 492b52b826..4110250555 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 @@ -2,21 +2,26 @@ package com.tangem.features.onramp.providers.model.previewData import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.onramp.model.OnrampPaymentMethod +import com.tangem.domain.onramp.model.PaymentMethodType import com.tangem.features.onramp.providers.entity.ProviderListItemUM -import com.tangem.features.onramp.providers.entity.ProviderListPaymentMethodUM -import com.tangem.features.onramp.providers.entity.ProviderListUM +import com.tangem.features.onramp.providers.entity.SelectPaymentAndProviderUM +import com.tangem.features.onramp.providers.entity.SelectProviderUM import kotlinx.collections.immutable.persistentListOf internal object SelectProviderPreviewData { - val state = ProviderListUM( - paymentMethod = ProviderListPaymentMethodUM( + private val paymentMethods = persistentListOf( + OnrampPaymentMethod( id = "card", name = "Card", imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png", - onClick = {}, - enabled = false, + type = PaymentMethodType.CARD, ), + ) + + private val providerListUM = SelectProviderUM( + paymentMethod = paymentMethods.first(), providers = persistentListOf( ProviderListItemUM.Available( providerId = "mercuryo", @@ -46,4 +51,11 @@ internal object SelectProviderPreviewData { ), ), ) + + val state = SelectPaymentAndProviderUM( + paymentMethods = paymentMethods, + selectedPaymentMethod = providerListUM, + isPaymentMethodClickEnabled = false, + onPaymentMethodClick = {}, + ) } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt index 45ca653245..9e444aee80 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt @@ -32,11 +32,11 @@ import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.domain.onramp.model.OnrampPaymentMethod import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.paymentmethod.ui.PaymentMethodIcon import com.tangem.features.onramp.providers.entity.ProviderListItemUM -import com.tangem.features.onramp.providers.entity.ProviderListPaymentMethodUM -import com.tangem.features.onramp.providers.entity.ProviderListUM +import com.tangem.features.onramp.providers.entity.SelectPaymentAndProviderUM import com.tangem.features.onramp.providers.model.previewData.SelectProviderPreviewData import com.tangem.features.onramp.utils.selectedBorder @@ -50,7 +50,7 @@ internal fun SelectProviderBottomSheet(config: TangemBottomSheetConfig, content: } @Composable -internal fun SelectProviderBottomSheetContent(state: ProviderListUM, modifier: Modifier = Modifier) { +internal fun SelectProviderBottomSheetContent(state: SelectPaymentAndProviderUM, modifier: Modifier = Modifier) { Column( modifier = modifier .background(TangemTheme.colors.background.primary) @@ -65,16 +65,23 @@ internal fun SelectProviderBottomSheetContent(state: ProviderListUM, modifier: M ) PaymentMethodBlock( modifier = Modifier.padding(top = TangemTheme.dimens.spacing20, bottom = TangemTheme.dimens.spacing16), - state = state.paymentMethod, + state = state.selectedPaymentMethod.paymentMethod, + isClickEnabled = state.isPaymentMethodClickEnabled, + onPaymentMethodClick = state.onPaymentMethodClick, ) - state.providers.fastForEach { provider -> + state.selectedPaymentMethod.providers.fastForEach { provider -> key(provider.providerId) { ProviderItem(state = provider) } } } } @Composable -private fun PaymentMethodBlock(state: ProviderListPaymentMethodUM, modifier: Modifier = Modifier) { +private fun PaymentMethodBlock( + state: OnrampPaymentMethod, + isClickEnabled: Boolean, + onPaymentMethodClick: () -> Unit, + modifier: Modifier = Modifier, +) { Row( modifier = modifier .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) @@ -83,7 +90,7 @@ private fun PaymentMethodBlock(state: ProviderListPaymentMethodUM, modifier: Mod color = TangemTheme.colors.stroke.secondary, shape = RoundedCornerShape(TangemTheme.dimens.radius16), ) - .clickable(enabled = state.enabled, onClick = state.onClick) + .clickable(enabled = isClickEnabled, onClick = onPaymentMethodClick) .padding(all = TangemTheme.dimens.spacing12), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), @@ -247,7 +254,7 @@ private fun UnavailableProviderItem( @Composable private fun SelectProviderBottomSheetContent_Preview( @PreviewParameter(SelectProviderBottomSheetContentPreviewProvider::class) - data: ProviderListUM, + data: SelectPaymentAndProviderUM, ) { TangemThemePreview { SelectProviderBottomSheetContent(data) @@ -255,8 +262,8 @@ private fun SelectProviderBottomSheetContent_Preview( } private class SelectProviderBottomSheetContentPreviewProvider : - PreviewParameterProvider { - override val values: Sequence + PreviewParameterProvider { + override val values: Sequence get() = sequenceOf( SelectProviderPreviewData.state, )