diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/SwapPairsWithProviders.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/SwapPairsWithProviders.kt new file mode 100644 index 0000000000..41568fb92f --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/SwapPairsWithProviders.kt @@ -0,0 +1,6 @@ +package com.tangem.datasource.api.express.models.response + +class SwapPairsWithProviders( + val swapPair: List, + val providers: List, +) \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index a3d9b7aa92..72b4687f50 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -9,6 +9,8 @@ import com.tangem.data.tokens.utils.CryptoCurrencyFactory import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.express.models.request.PairsRequestBody +import com.tangem.datasource.api.express.models.response.SwapPair +import com.tangem.datasource.api.express.models.response.SwapPairsWithProviders import com.tangem.datasource.api.oneinch.OneInchApi import com.tangem.datasource.api.oneinch.OneInchApiFactory import com.tangem.datasource.api.oneinch.OneInchErrorsHandler @@ -50,7 +52,6 @@ internal class SwapRepositoryImpl @Inject constructor( private val swapConverter = SwapConverter() private val leastTokenInfoConverter = LeastTokenInfoConverter() private val swapPairInfoConverter = SwapPairInfoConverter() - private val swapProviderConverter = SwapProviderConverter() private val cryptoCurrencyFactory = CryptoCurrencyFactory() override suspend fun getPairs( @@ -78,34 +79,29 @@ internal class SwapRepositoryImpl @Inject constructor( ) } - pairs.await() + reversedPairs.await() - } - } + val allPairs = pairs.await() + reversedPairs.await() - override suspend fun getProvidersDetails(providers: Set): List { - val providersMap = providers.associateBy { it.providerId } - return tangemExpressApi.getProviders().getOrThrow().mapNotNull { - val provider = providersMap[it.id] - if (provider != null) { - swapProviderConverter.convert(it).copy(rateTypes = provider.rateTypes) - } else { - null - } + val providers = tangemExpressApi.getProviders().getOrThrow() + + return@withContext swapPairInfoConverter.convert( + SwapPairsWithProviders( + swapPair = allPairs, + providers = providers, + ), + ) } } private suspend fun getPairsInternal( from: List, to: List, - ): List { + ): List { return tangemExpressApi.getPairs( PairsRequestBody( from = from, to = to, ), - ) - .getOrThrow() - .map { swapPairInfoConverter.convert(it) } + ).getOrThrow() } override suspend fun getRates(currencyId: String, tokenIds: List): Map { diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapPairInfoConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapPairInfoConverter.kt index c205bec15e..ff86a7e049 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapPairInfoConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapPairInfoConverter.kt @@ -1,36 +1,53 @@ package com.tangem.feature.swap.converters -import com.tangem.datasource.api.express.models.response.SwapPair -import com.tangem.datasource.api.express.models.response.SwapPairProvider +import com.tangem.datasource.api.express.models.response.* import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo +import com.tangem.utils.converter.Converter +import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType as ExchangeProviderTypeDomain import com.tangem.feature.swap.domain.models.domain.SwapPairLeast as SwapPairDomain import com.tangem.feature.swap.domain.models.domain.SwapProvider as SwapPairProviderDomain -import com.tangem.utils.converter.Converter -class SwapPairInfoConverter : Converter { +class SwapPairInfoConverter : Converter> { private val rateTypeConverter = RateTypeConverter() - override fun convert(value: SwapPair): SwapPairDomain { - return SwapPairDomain( - from = LeastTokenInfo( - contractAddress = value.from.contractAddress, - network = value.from.network, - ), - to = LeastTokenInfo( - contractAddress = value.to.contractAddress, - network = value.to.network, - ), - providers = value.providers.map { - convertProvider(it) - }, - ) + override fun convert(value: SwapPairsWithProviders): List { + val providersAdditionalMap = value.providers.associateBy { it.id } + return value.swapPair.map { pair -> + SwapPairDomain( + from = LeastTokenInfo( + contractAddress = pair.from.contractAddress, + network = pair.from.network, + ), + to = LeastTokenInfo( + contractAddress = pair.to.contractAddress, + network = pair.to.network, + ), + providers = pair.providers.mapNotNull { + convertProvider(it, providersAdditionalMap) + }, + ) + } } - private fun convertProvider(swapPairProvider: SwapPairProvider): SwapPairProviderDomain { + private fun convertProvider( + swapPairProvider: SwapPairProvider, + providerAdditional: Map, + ): SwapPairProviderDomain? { + val additionalProvider = providerAdditional[swapPairProvider.providerId] ?: return null return SwapPairProviderDomain( providerId = swapPairProvider.providerId, rateTypes = swapPairProvider.rateTypes.map { rateTypeConverter.convert(it) }, + name = additionalProvider.name, + type = convertExchangeType(additionalProvider.type), + imageLarge = additionalProvider.imageLargeUrl, ) } + + private fun convertExchangeType(type: ExchangeProviderType): ExchangeProviderTypeDomain { + return when (type) { + ExchangeProviderType.DEX -> ExchangeProviderTypeDomain.DEX + ExchangeProviderType.CEX -> ExchangeProviderTypeDomain.CEX + } + } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapProviderConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapProviderConverter.kt deleted file mode 100644 index 6930923128..0000000000 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapProviderConverter.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.feature.swap.converters - -import com.tangem.datasource.api.express.models.response.ExchangeProvider -import com.tangem.datasource.api.express.models.response.ExchangeProviderType -import com.tangem.feature.swap.domain.models.domain.SwapProvider -import com.tangem.utils.converter.Converter -import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType as ExchangeProviderTypeDomain - -class SwapProviderConverter : Converter { - override fun convert(value: ExchangeProvider): SwapProvider { - return SwapProvider( - providerId = value.id, - name = value.name, - type = convertExchangeType(value.type), - imageLarge = value.imageLargeUrl, - ) - } - - private fun convertExchangeType(type: ExchangeProviderType): ExchangeProviderTypeDomain { - return when (type) { - ExchangeProviderType.DEX -> ExchangeProviderTypeDomain.DEX - ExchangeProviderType.CEX -> ExchangeProviderTypeDomain.CEX - } - } -} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index a43fd0295d..5c185a5cff 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -152,16 +152,7 @@ internal class SwapInteractorImpl @Inject constructor( initialCurrency: LeastTokenInfo, currenciesList: List, ): List { - val pairs = repository.getPairs(initialCurrency, currenciesList) - val providers = pairs.flatMap { it.providers }.toSet() - val updatedProviders = repository.getProvidersDetails(providers).associateBy { it.providerId } - return pairs.map { pair -> - pair.copy( - providers = pair.providers.mapNotNull { currentProvider -> - updatedProviders[currentProvider.providerId] - }, - ) - } + return repository.getPairs(initialCurrency, currenciesList) } @Deprecated("used in old swap mechanism") diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index 60d4275364..31863dc852 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -10,8 +10,6 @@ interface SwapRepository { suspend fun getPairs(initialCurrency: LeastTokenInfo, currencyList: List): List - suspend fun getProvidersDetails(providers: Set): List - suspend fun getRates(currencyId: String, tokenIds: List): Map suspend fun getExchangeableTokens(networkId: String): List diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapPairLeast.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapPairLeast.kt index 76c2c0795d..76fa9a8238 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapPairLeast.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapPairLeast.kt @@ -29,9 +29,9 @@ data class CryptoCurrencySwapInfo( data class SwapProvider( val providerId: String, val rateTypes: List = emptyList(), - val name: String? = null, - val type: ExchangeProviderType? = null, - val imageLarge: String? = null, + val name: String, + val type: ExchangeProviderType, + val imageLarge: String, ) enum class ExchangeProviderType { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ChooseProviderBottomSheetConfig.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ChooseProviderBottomSheetConfig.kt index 7507686111..68c761cd14 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ChooseProviderBottomSheetConfig.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ChooseProviderBottomSheetConfig.kt @@ -3,7 +3,7 @@ package com.tangem.feature.swap.models.states import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import kotlinx.collections.immutable.ImmutableList -class ChooseProviderBottomSheetConfig( +data class ChooseProviderBottomSheetConfig( val selectedProviderId: String, val providers: ImmutableList, ) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt index e93e82c505..6d2bab0f4f 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt @@ -1,12 +1,15 @@ package com.tangem.feature.swap.ui import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet @@ -52,17 +55,24 @@ private fun ChooseProviderBottomSheetContent(content: ChooseProviderBottomSheetC .background( color = TangemTheme.colors.background.action, shape = TangemTheme.shapes.roundedCornersXMedium, - ), + ) + .clip(shape = TangemTheme.shapes.roundedCornersXMedium), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { - content.providers.forEach { - val isSelected = it.id == content.selectedProviderId + content.providers.forEach { provider -> + val isSelected = provider.id == content.selectedProviderId ProviderItem( - state = it, + state = provider, isSelected = isSelected, - modifier = Modifier.padding( - horizontal = TangemTheme.dimens.spacing12, - vertical = TangemTheme.dimens.spacing12, - ), + modifier = Modifier + .clickable( + enabled = provider.onProviderClick != null, + onClick = { provider.onProviderClick?.invoke(provider.id) }, + ) + .padding( + horizontal = TangemTheme.dimens.spacing12, + vertical = TangemTheme.dimens.spacing12, + ), ) } } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt index 1cd636d3e7..5d75293da0 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt @@ -1,7 +1,6 @@ package com.tangem.feature.swap.ui import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon @@ -35,9 +34,9 @@ private val GrayscaleColorFilter: ColorFilter get() = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(GRAY_SCALE_SATURATION) }) @Composable -fun ProviderItemBlock(state: ProviderState) { +fun ProviderItemBlock(state: ProviderState, modifier: Modifier = Modifier) { if (state !is ProviderState.Empty) { - BaseContainer(state) { + BaseContainer(modifier = modifier) { ProviderItem( state = state, modifier = Modifier.align(Alignment.CenterStart), @@ -279,17 +278,14 @@ private fun BoxScope.ProviderChevron(state: ProviderState.Content, isSelected: B } @Composable -private fun BaseContainer(state: ProviderState, content: @Composable BoxScope.() -> Unit) { +private fun BaseContainer(modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit) { Box( - modifier = Modifier + modifier = modifier .background( color = TangemTheme.colors.background.action, shape = TangemTheme.shapes.roundedCornersXMedium, ) - .clickable( - enabled = state.onProviderClick != null, - onClick = { state.onProviderClick?.invoke(state.id) }, - ) + .clip(shape = TangemTheme.shapes.roundedCornersXMedium) .fillMaxWidth() .defaultMinSize(minHeight = TangemTheme.dimens.size68), ) { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 4033c21dbc..e7623069ab 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -270,6 +270,7 @@ internal class StateBuilder( providerState = swapProvider.convertToContentClickableProviderState( fromTokenInfo = quoteModel.fromTokenInfo, toTokenInfo = quoteModel.toTokenInfo, + selectionType = ProviderState.SelectionType.CLICK, onProviderClick = actions.onProviderClick, ), ) @@ -759,6 +760,21 @@ internal class StateBuilder( ) } + fun updateSelectedProvider(uiState: SwapStateHolder, selectedProviderId: String): SwapStateHolder { + val config = uiState.bottomSheetConfig?.content as? ChooseProviderBottomSheetConfig + return if (config != null) { + uiState.copy( + bottomSheetConfig = uiState.bottomSheetConfig.copy( + content = config.copy( + selectedProviderId = selectedProviderId, + ), + ), + ) + } else { + uiState + } + } + private fun Map.Entry.convertToProviderState( onProviderSelect: (String) -> Unit, ): ProviderState? { @@ -766,9 +782,10 @@ internal class StateBuilder( return when (val state = this.value) { is SwapState.EmptyAmountState -> null is SwapState.QuotesLoadedState -> provider.convertToContentClickableProviderState( - state.fromTokenInfo, - state.toTokenInfo, + fromTokenInfo = state.fromTokenInfo, + toTokenInfo = state.toTokenInfo, onProviderClick = onProviderSelect, + selectionType = ProviderState.SelectionType.SELECT, ) is SwapState.SwapError -> null } @@ -806,6 +823,7 @@ internal class StateBuilder( private fun SwapProvider.convertToContentClickableProviderState( fromTokenInfo: TokenSwapInfo, toTokenInfo: TokenSwapInfo, + selectionType: ProviderState.SelectionType, onProviderClick: (String) -> Unit, ): ProviderState { val rate = toTokenInfo.tokenAmount.value.divide( @@ -818,12 +836,12 @@ internal class StateBuilder( val rateString = "1 $fromCurrencySymbol ≈ $rate $toCurrencySymbol" return ProviderState.Content( id = this.providerId, - name = this.name ?: "", - iconUrl = this.imageLarge ?: "", + name = this.name, + iconUrl = this.imageLarge, type = this.type.toString(), rate = rateString, additionalBadge = ProviderState.AdditionalBadge.BestTrade, - selectionType = ProviderState.SelectionType.CLICK, + selectionType = selectionType, percentLowerThenBest = null, onProviderClick = onProviderClick, ) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt index 9d59406885..95003050c3 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt @@ -1,7 +1,6 @@ package com.tangem.feature.swap.ui import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.padding import androidx.compose.material.* import androidx.compose.material3.* @@ -12,7 +11,6 @@ import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig import com.tangem.feature.swap.models.states.GivePermissionBottomSheetConfig -@OptIn(ExperimentalLayoutApi::class) @Composable internal fun SwapScreen(stateHolder: SwapStateHolder) { BackHandler(onBack = stateHolder.onBackClicked) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 1ad3351901..59a9ab4cfd 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -68,7 +68,14 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi ) { MainInfo(state) - ProviderItemBlock(state = state.providerState) + ProviderItemBlock( + state = state.providerState, + modifier = Modifier + .clickable( + enabled = state.providerState.onProviderClick != null, + onClick = { state.providerState.onProviderClick?.invoke(state.providerState.id) }, + ), + ) FeeItem(feeState = state.fee, currency = state.networkCurrency) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index cca50c074d..1d119785f8 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -621,6 +621,7 @@ internal class SwapViewModel @Inject constructor( val swapState = dataState.lastLoadedSwapStates[provider] val fromToken = dataState.fromCryptoCurrency if (provider != null && swapState != null && fromToken != null) { + uiState = stateBuilder.updateSelectedProvider(uiState, provider.providerId) setupLoadedState( provider = provider, state = swapState,