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 00d5f2448a..f63f42e5f3 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 @@ -43,11 +43,9 @@ internal class SwapRepositoryImpl @Inject constructor( ) : SwapRepository { private val tokensConverter = TokensConverter() - private val quotesConverter = QuotesConverter() private val swapConverter = SwapConverter() private val leastTokenInfoConverter = LeastTokenInfoConverter() private val swapPairInfoConverter = SwapPairInfoConverter() - private val rateTypeConverter = RateTypeConverter() private val swapProviderConverter = SwapProviderConverter() override suspend fun getPairs( 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 1dd4dab66c..28de49180c 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 @@ -389,27 +389,6 @@ internal class SwapInteractorImpl @Inject constructor( } } - @Deprecated("used in old swap mechanism") - private fun selectToToken( - initialToken: Currency, - tokensInWallet: List, - loadedTokens: List, - ): Currency { - val toToken = if (tokensInWallet.isNotEmpty()) { - tokensInWallet.firstOrNull { it.symbol != initialToken.symbol } - ?: loadedTokens.first { it.symbol != initialToken.symbol } - } else { - val findUsdt = loadedTokens.firstOrNull { it.symbol == USDT_SYMBOL && it.symbol != initialToken.symbol } - if (findUsdt == null) { - val findUsdc = loadedTokens.firstOrNull { it.symbol == USDC_SYMBOL && it.symbol != initialToken.symbol } - findUsdc ?: loadedTokens.first { it.symbol != initialToken.symbol } - } else { - findUsdt - } - } - return toToken - } - @Suppress("UnusedPrivateMember") @Deprecated("used in old swap mechanism") private fun getTokensWithBalance( @@ -890,8 +869,6 @@ internal class SwapInteractorImpl @Inject constructor( @Suppress("UnusedPrivateMember") private const val INCREASE_FEE_TO_CHECK_ENOUGH_PERCENT = 1.0 // if need to increase fee when check isEnough private const val INCREASE_GAS_LIMIT_BY = 112 // 12% - private const val USDT_SYMBOL = "USDT" - private const val USDC_SYMBOL = "USDC" private const val INFINITY_SYMBOL = "∞" private val ONE_INCH_SUPPORTED_NETWORKS = listOf( diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index 86879ce609..dc0cdcabd2 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -37,6 +37,7 @@ dependencies { implementation(deps.arrow.core) implementation(deps.compose.foundation) implementation(deps.compose.material) + implementation(deps.compose.material3) implementation(deps.compose.ui.tooling) implementation(deps.compose.coil) implementation(deps.compose.constraintLayout) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index e166729104..82b62f10c7 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -1,6 +1,7 @@ package com.tangem.feature.swap.models import androidx.compose.ui.text.input.TextFieldValue +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.components.states.Item import com.tangem.core.ui.components.states.SelectableItemsState @@ -22,6 +23,7 @@ data class SwapStateHolder( val permissionState: SwapPermissionState = SwapPermissionState.Empty, val successState: SwapSuccessStateHolder? = null, val selectTokenState: SwapSelectTokenStateHolder? = null, + val bottomSheetConfig: TangemBottomSheetConfig? = null, val swapButton: SwapButton, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt index 0dc67646d0..7beb38ac1e 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt @@ -21,4 +21,6 @@ data class UiActions( // region new actions val onClickFee: () -> Unit, val onSelectFeeType: (FeeType) -> Unit, + val onProviderClick: (String) -> Unit, + val onProviderSelect: (String) -> Unit, ) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/GivePermissionBottomSheetConfig.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/GivePermissionBottomSheetConfig.kt new file mode 100644 index 0000000000..57628ded51 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/GivePermissionBottomSheetConfig.kt @@ -0,0 +1,9 @@ +package com.tangem.feature.swap.models.states + +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.feature.swap.models.SwapPermissionState + +class GivePermissionBottomSheetConfig( + val data: SwapPermissionState.ReadyForRequest, + val onCancel: () -> Unit, +) : 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 266c1f9b49..e93e82c505 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 @@ -26,7 +26,7 @@ fun ChooseProviderBottomSheet(config: TangemBottomSheetConfig) { @Composable private fun ChooseProviderBottomSheetContent(content: ChooseProviderBottomSheetConfig) { Column( - modifier = Modifier.background(TangemTheme.colors.background.secondary), + modifier = Modifier.background(TangemTheme.colors.background.primary), ) { Text( text = "Choose provider", 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 dfe32ead57..1cd636d3e7 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 @@ -324,7 +324,7 @@ private fun BestTradeItem(modifier: Modifier = Modifier) { ), ) { Text( - text = "Best trade", + text = "Best rate", style = TangemTheme.typography.caption1, color = TangemTheme.colors.icon.accent, modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing6), 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 4cbd99d028..4089832235 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 @@ -3,6 +3,7 @@ package com.tangem.feature.swap.ui import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue import com.tangem.common.Provider +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.components.states.Item import com.tangem.core.ui.components.states.SelectableItemsState @@ -21,11 +22,14 @@ import com.tangem.feature.swap.domain.models.domain.SwapProvider import com.tangem.feature.swap.domain.models.formatToUIRepresentation import com.tangem.feature.swap.domain.models.ui.* import com.tangem.feature.swap.models.* +import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig +import com.tangem.feature.swap.models.states.GivePermissionBottomSheetConfig import com.tangem.feature.swap.models.states.ProviderState import com.tangem.feature.swap.presentation.R import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import java.math.BigDecimal +import java.math.RoundingMode /** * State builder creates a specific states for SwapScreen @@ -224,7 +228,7 @@ internal class StateBuilder( sendCardData = SwapCardState.SwapCardData( type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard), amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue, - amountEquivalent = fromCurrencyStatus.getFormattedAmount(quoteModel.fromTokenInfo.amountFiat), + amountEquivalent = getFormattedFiatAmount(quoteModel.fromTokenInfo.amountFiat), tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl, coinId = fromCurrencyStatus.currency.network.backendId, isNotNativeToken = uiStateHolder.sendCardData.isNotNativeToken, @@ -236,7 +240,7 @@ internal class StateBuilder( receiveCardData = SwapCardState.SwapCardData( type = TransactionCardType.ReceiveCard(), amountTextFieldValue = TextFieldValue(quoteModel.toTokenInfo.tokenAmount.formatToUIRepresentation()), - amountEquivalent = toCurrencyStatus.getFormattedAmount(quoteModel.toTokenInfo.amountFiat), + amountEquivalent = getFormattedFiatAmount(quoteModel.toTokenInfo.amountFiat), tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl, coinId = toCurrencyStatus.currency.network.backendId, isNotNativeToken = uiStateHolder.receiveCardData.isNotNativeToken, @@ -266,6 +270,7 @@ internal class StateBuilder( providerState = swapProvider.convertToContentClickableProviderState( fromTokenInfo = quoteModel.fromTokenInfo, toTokenInfo = quoteModel.toTokenInfo, + onProviderClick = actions.onProviderClick, ), ) } @@ -709,6 +714,66 @@ internal class StateBuilder( } } + fun showPermissionBottomSheet(uiState: SwapStateHolder, onDismiss: () -> Unit): SwapStateHolder { + val permissionState = uiState.permissionState + if (permissionState is SwapPermissionState.ReadyForRequest) { + val config = GivePermissionBottomSheetConfig( + data = permissionState, + onCancel = onDismiss, + ) + return uiState.copy( + bottomSheetConfig = TangemBottomSheetConfig( + isShow = true, + onDismissRequest = onDismiss, + content = config, + ), + ) + } + return uiState + } + + fun dismissBottomSheet(uiState: SwapStateHolder): SwapStateHolder { + return uiState.copy( + bottomSheetConfig = uiState.bottomSheetConfig?.copy(isShow = false), + ) + } + + fun showSelectProviderBottomSheet( + uiState: SwapStateHolder, + selectedProviderId: String, + providersStates: Map, + onDismiss: () -> Unit, + ): SwapStateHolder { + val config = ChooseProviderBottomSheetConfig( + selectedProviderId = selectedProviderId, + providers = providersStates.entries + .mapNotNull { it.convertToProviderState(actions.onProviderSelect) } + .toImmutableList(), + ) + return uiState.copy( + bottomSheetConfig = TangemBottomSheetConfig( + isShow = true, + onDismissRequest = onDismiss, + content = config, + ), + ) + } + + private fun Map.Entry.convertToProviderState( + onProviderSelect: (String) -> Unit, + ): ProviderState? { + val provider = this.key + return when (val state = this.value) { + is SwapState.EmptyAmountState -> null + is SwapState.QuotesLoadedState -> provider.convertToContentClickableProviderState( + state.fromTokenInfo, + state.toTokenInfo, + onProviderClick = onProviderSelect, + ) + is SwapState.SwapError -> null + } + } + private fun createPermissionNotificationConfig(fromTokenSymbol: String): NotificationConfig { return NotificationConfig( title = resourceReference(R.string.swapping_permission_header), @@ -724,7 +789,7 @@ internal class StateBuilder( return NotificationConfig( title = resourceReference(R.string.swapping_high_price_impact), subtitle = resourceReference(R.string.swapping_high_price_impact_description), - iconResId = R.drawable.ic_locked_24, + iconResId = R.drawable.ic_alert_circle_24, ) } @@ -741,8 +806,13 @@ internal class StateBuilder( private fun SwapProvider.convertToContentClickableProviderState( fromTokenInfo: TokenSwapInfo, toTokenInfo: TokenSwapInfo, + onProviderClick: (String) -> Unit, ): ProviderState { - val rate = fromTokenInfo.tokenAmount.value / toTokenInfo.tokenAmount.value + val rate = toTokenInfo.tokenAmount.value.divide( + fromTokenInfo.tokenAmount.value, + toTokenInfo.cryptoCurrencyStatus.currency.decimals, + RoundingMode.HALF_UP, + ) val fromCurrencySymbol = fromTokenInfo.cryptoCurrencyStatus.currency.symbol val toCurrencySymbol = toTokenInfo.cryptoCurrencyStatus.currency.symbol val rateString = "1 $fromCurrencySymbol ≈ $rate $toCurrencySymbol" @@ -755,7 +825,7 @@ internal class StateBuilder( additionalBadge = ProviderState.AdditionalBadge.BestTrade, selectionType = ProviderState.SelectionType.CLICK, percentLowerThenBest = null, - onProviderClick = {}, + onProviderClick = onProviderClick, ) } @@ -765,10 +835,6 @@ internal class StateBuilder( return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals) } - private fun CryptoCurrencyStatus.getFormattedAmount(amount: BigDecimal): String { - return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals) - } - @Suppress("UnusedPrivateMember") private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String { val fiatAmount = value.fiatAmount ?: return UNKNOWN_AMOUNT_SIGN @@ -777,6 +843,12 @@ internal class StateBuilder( return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol) } + private fun getFormattedFiatAmount(amount: BigDecimal): String { + val appCurrency = appCurrencyProvider() + + return BigDecimalFormatter.formatFiatAmount(amount, appCurrency.code, appCurrency.symbol) + } + private companion object { const val ADDRESS_MIN_LENGTH = 11 const val ADDRESS_FIRST_PART_LENGTH = 7 diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt new file mode 100644 index 0000000000..724c3e44d8 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt @@ -0,0 +1,327 @@ +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.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.* +import com.tangem.core.ui.components.atoms.Hand +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.swap.models.ApprovePermissionButton +import com.tangem.feature.swap.models.ApproveType +import com.tangem.feature.swap.models.CancelPermissionButton +import com.tangem.feature.swap.models.SwapPermissionState +import com.tangem.feature.swap.models.states.GivePermissionBottomSheetConfig +import com.tangem.feature.swap.presentation.R +import kotlinx.collections.immutable.ImmutableList + +@Composable +fun SwapPermissionBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet(config) { content: GivePermissionBottomSheetConfig -> + SwapPermissionBottomSheetContent(content = content) + } +} + +@Composable +private fun SwapPermissionBottomSheetContent(content: GivePermissionBottomSheetConfig) { + var isPermissionAlertShow by remember { mutableStateOf(false) } + val data = content.data + Column( + modifier = Modifier + .background(color = TangemTheme.colors.background.primary) + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing16), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Hand() + + SpacerH10() + + Box(modifier = Modifier.fillMaxWidth()) { + Text( + modifier = Modifier.align(Alignment.Center), + text = stringResource(id = R.string.swapping_permission_header), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + ) + IconButton( + modifier = Modifier.align(Alignment.CenterEnd), + onClick = { isPermissionAlertShow = true }, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_question_24), + contentDescription = null, + ) + } + } + + SpacerH10() + + Text( + text = stringResource( + id = R.string.swapping_permission_subheader, + data.currency, + ), + color = TangemTheme.colors.text.secondary, + style = TangemTheme.typography.body2, + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing8), + ) + + SpacerH16() + + ApprovalBottomSheetInfo(data) + + SpacerH28() + + PrimaryButtonIconEnd( + text = stringResource(id = R.string.swapping_permission_buttons_approve), + iconResId = R.drawable.ic_tangem_24, + modifier = Modifier.fillMaxWidth(), + onClick = data.approveButton.onClick, + ) + + SpacerH12() + + SecondaryButton( + text = stringResource(id = R.string.common_cancel), + modifier = Modifier.fillMaxWidth(), + onClick = { + content.onCancel() + }, + ) + + SpacerH16() + + // region dialog + if (isPermissionAlertShow) { + BasicDialog( + message = stringResource(id = R.string.swapping_approve_information_text), + title = stringResource(id = R.string.swapping_approve_information_title), + confirmButton = DialogButton { isPermissionAlertShow = false }, + onDismissDialog = {}, + ) + } + } +} + +@Composable +private fun ApprovalBottomSheetInfo(data: SwapPermissionState.ReadyForRequest) { + Column( + modifier = Modifier + .background(color = TangemTheme.colors.background.primary) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + AmountItem( + currency = data.currency, + approveType = data.approveType, + onChangeApproveType = data.onChangeApproveType, + approveItems = data.approveItems, + ) + SubtitleItem( + subtitle = stringResource(id = R.string.swapping_permission_policy_type_footer), + modifier = Modifier.fillMaxWidth(), + ) + SpacerH24() + DividerBottomSheet() + FeeItem(fee = data.fee.resolveReference()) + SubtitleItem( + subtitle = stringResource(id = R.string.swapping_permission_fee_footer), + modifier = Modifier.fillMaxWidth(), + ) + } +} + +@Composable +private fun DividerBottomSheet() { + Divider( + color = TangemTheme.colors.stroke.primary, + thickness = TangemTheme.dimens.size0_5, + ) +} + +@Composable +private fun InformationItem(subtitle: String, value: String) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = TangemTheme.dimens.spacing16), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = subtitle, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + maxLines = 1, + ) + + MiddleEllipsisText( + text = value, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), + ) + } +} + +@Composable +private fun AmountItem( + currency: String, + approveType: ApproveType, + approveItems: ImmutableList, + onChangeApproveType: (ApproveType) -> Unit, +) { + var isExpandSelector by remember { + mutableStateOf(false) + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = TangemTheme.dimens.spacing16), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(id = R.string.swapping_permission_rows_amount, currency), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + maxLines = 1, + ) + Box { + SelectorItem( + getTitleForApproveType(approveType = approveType), + ) { + isExpandSelector = true + } + DropdownSelector( + isExpanded = isExpandSelector, + onDismiss = { isExpandSelector = false }, + onItemClick = { approveType -> + isExpandSelector = false + onChangeApproveType.invoke(approveType) + }, + items = approveItems, + ) + } + } +} + +@Composable +private fun SelectorItem(title: String, onClick: () -> Unit) { + Row( + modifier = Modifier.clickable { onClick() }, + ) { + Text( + text = title, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.body1, + maxLines = 1, + ) + Icon( + painter = painterResource(id = R.drawable.ic_chevron_24), + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + } +} + +@Composable +private fun DropdownSelector( + isExpanded: Boolean, + onDismiss: () -> Unit, + onItemClick: (ApproveType) -> Unit, + items: ImmutableList, +) { + DropdownMenu( + expanded = isExpanded, + onDismissRequest = onDismiss, + modifier = Modifier + .wrapContentSize() + .background(TangemTheme.colors.background.secondary), + ) { + items.forEach { item -> + DropdownMenuItem( + onClick = { + onItemClick.invoke(item) + }, + ) { + Text( + text = getTitleForApproveType(approveType = item), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.body1, + maxLines = 1, + ) + } + } + } +} + +@Composable +private fun FeeItem(fee: String) { + InformationItem( + subtitle = stringResource(id = R.string.send_fee_label), + value = fee, + ) +} + +@Composable +private fun SubtitleItem(subtitle: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier, + text = subtitle, + color = TangemTheme.colors.text.secondary, + style = TangemTheme.typography.body2, + ) +} + +@Composable +private fun getTitleForApproveType(approveType: ApproveType): String = when (approveType) { + ApproveType.LIMITED -> stringResource(id = R.string.swapping_permission_current_transaction) + ApproveType.UNLIMITED -> stringResource(id = R.string.swapping_permission_unlimited) +} + +// region preview + +@Preview +@Composable +private fun Preview_AgreementBottomSheet_InLightTheme() { + TangemTheme(isDark = false) { + SwapPermissionBottomSheetContent(content = previewData) + } +} + +@Preview +@Composable +private fun Preview_AgreementBottomSheet_InDarkTheme() { + TangemTheme(isDark = true) { + SwapPermissionBottomSheetContent(content = previewData) + } +} + +private val previewData = GivePermissionBottomSheetConfig( + data = SwapPermissionState.ReadyForRequest( + currency = "DAI", + amount = "∞", + walletAddress = "", + spenderAddress = "", + fee = TextReference.Str("2,14$"), + approveType = ApproveType.UNLIMITED, + approveButton = ApprovePermissionButton(true) {}, + cancelButton = CancelPermissionButton(true), + onChangeApproveType = { ApproveType.UNLIMITED }, + ), + onCancel = {}, +) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt index 6f8bee6fe7..617534b273 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheetContent.kt @@ -1,315 +1,297 @@ 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.* import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.* -import com.tangem.core.ui.components.atoms.Hand -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.swap.models.ApprovePermissionButton -import com.tangem.feature.swap.models.ApproveType -import com.tangem.feature.swap.models.CancelPermissionButton -import com.tangem.feature.swap.models.SwapPermissionState -import com.tangem.feature.swap.presentation.R -import kotlinx.collections.immutable.ImmutableList -@Composable -fun SwapPermissionBottomSheetContent(data: SwapPermissionState.ReadyForRequest, onCancel: () -> Unit) { - var isPermissionAlertShow by remember { mutableStateOf(false) } - Column( - modifier = Modifier - .background(color = TangemTheme.colors.background.primary) - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.spacing16), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Hand() +// @Composable +// fun SwapPermissionBottomSheetContent(data: SwapPermissionState.ReadyForRequest, onCancel: () -> Unit) { +// var isPermissionAlertShow by remember { mutableStateOf(false) } +// Column( +// modifier = Modifier +// .background(color = TangemTheme.colors.background.primary) +// .fillMaxWidth() +// .padding(horizontal = TangemTheme.dimens.spacing16), +// horizontalAlignment = Alignment.CenterHorizontally, +// ) { +// Hand() +// +// SpacerH10() +// +// Box(modifier = Modifier.fillMaxWidth()) { +// Text( +// modifier = Modifier.align(Alignment.Center), +// text = stringResource(id = R.string.swapping_permission_header), +// color = TangemTheme.colors.text.primary1, +// style = TangemTheme.typography.subtitle1, +// ) +// IconButton( +// modifier = Modifier.align(Alignment.CenterEnd), +// onClick = { isPermissionAlertShow = true }, +// ) { +// Icon( +// painter = painterResource(id = R.drawable.ic_question_24), +// contentDescription = null, +// ) +// } +// } +// +// SpacerH10() +// +// Text( +// text = stringResource( +// id = R.string.swapping_permission_subheader, +// data.currency, +// ), +// color = TangemTheme.colors.text.secondary, +// style = TangemTheme.typography.body2, +// textAlign = TextAlign.Center, +// modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing8), +// ) +// +// SpacerH16() +// +// ApprovalBottomSheetInfo(data) +// +// SpacerH28() +// +// PrimaryButtonIconEnd( +// text = stringResource(id = R.string.swapping_permission_buttons_approve), +// iconResId = R.drawable.ic_tangem_24, +// modifier = Modifier.fillMaxWidth(), +// onClick = data.approveButton.onClick, +// ) +// +// SpacerH12() +// +// SecondaryButton( +// text = stringResource(id = R.string.common_cancel), +// modifier = Modifier.fillMaxWidth(), +// onClick = { +// onCancel() +// }, +// ) +// +// SpacerH16() +// +// // region dialog +// if (isPermissionAlertShow) { +// BasicDialog( +// message = stringResource(id = R.string.swapping_approve_information_text), +// title = stringResource(id = R.string.swapping_approve_information_title), +// confirmButton = DialogButton { isPermissionAlertShow = false }, +// onDismissDialog = {}, +// ) +// } +// } +// } - SpacerH10() - - Box(modifier = Modifier.fillMaxWidth()) { - Text( - modifier = Modifier.align(Alignment.Center), - text = stringResource(id = R.string.swapping_permission_header), - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.subtitle1, - ) - IconButton( - modifier = Modifier.align(Alignment.CenterEnd), - onClick = { isPermissionAlertShow = true }, - ) { - Icon( - painter = painterResource(id = R.drawable.ic_question_24), - contentDescription = null, - ) - } - } - - SpacerH10() - - Text( - text = stringResource( - id = R.string.swapping_permission_subheader, - data.currency, - ), - color = TangemTheme.colors.text.secondary, - style = TangemTheme.typography.body2, - textAlign = TextAlign.Center, - modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing8), - ) - - SpacerH16() - - ApprovalBottomSheetInfo(data) - - SpacerH28() - - PrimaryButtonIconEnd( - text = stringResource(id = R.string.swapping_permission_buttons_approve), - iconResId = R.drawable.ic_tangem_24, - modifier = Modifier.fillMaxWidth(), - onClick = data.approveButton.onClick, - ) - - SpacerH12() - - SecondaryButton( - text = stringResource(id = R.string.common_cancel), - modifier = Modifier.fillMaxWidth(), - onClick = { - onCancel() - }, - ) - - SpacerH16() - - // region dialog - if (isPermissionAlertShow) { - BasicDialog( - message = stringResource(id = R.string.swapping_approve_information_text), - title = stringResource(id = R.string.swapping_approve_information_title), - confirmButton = DialogButton { isPermissionAlertShow = false }, - onDismissDialog = {}, - ) - } - } -} - -@Composable -private fun ApprovalBottomSheetInfo(data: SwapPermissionState.ReadyForRequest) { - Column( - modifier = Modifier - .background(color = TangemTheme.colors.background.primary) - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - AmountItem( - currency = data.currency, - approveType = data.approveType, - onChangeApproveType = data.onChangeApproveType, - approveItems = data.approveItems, - ) - SubtitleItem( - subtitle = stringResource(id = R.string.swapping_permission_policy_type_footer), - modifier = Modifier.fillMaxWidth(), - ) - SpacerH24() - DividerBottomSheet() - FeeItem(fee = data.fee.resolveReference()) - SubtitleItem( - subtitle = stringResource(id = R.string.swapping_permission_fee_footer), - modifier = Modifier.fillMaxWidth(), - ) - } -} - -@Composable -private fun DividerBottomSheet() { - Divider( - color = TangemTheme.colors.stroke.primary, - thickness = TangemTheme.dimens.size0_5, - ) -} - -@Composable -private fun InformationItem(subtitle: String, value: String) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = TangemTheme.dimens.spacing16), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = subtitle, - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.subtitle1, - maxLines = 1, - ) - - MiddleEllipsisText( - text = value, - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.body2, - modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), - ) - } -} - -@Composable -private fun AmountItem( - currency: String, - approveType: ApproveType, - approveItems: ImmutableList, - onChangeApproveType: (ApproveType) -> Unit, -) { - var isExpandSelector by remember { - mutableStateOf(false) - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = TangemTheme.dimens.spacing16), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = stringResource(id = R.string.swapping_permission_rows_amount, currency), - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.subtitle1, - maxLines = 1, - ) - Box { - SelectorItem( - getTitleForApproveType(approveType = approveType), - ) { - isExpandSelector = true - } - DropdownSelector( - isExpanded = isExpandSelector, - onDismiss = { isExpandSelector = false }, - onItemClick = { approveType -> - isExpandSelector = false - onChangeApproveType.invoke(approveType) - }, - items = approveItems, - ) - } - } -} - -@Composable -private fun SelectorItem(title: String, onClick: () -> Unit) { - Row( - modifier = Modifier.clickable { onClick() }, - ) { - Text( - text = title, - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.body1, - maxLines = 1, - ) - Icon( - painter = painterResource(id = R.drawable.ic_chevron_24), - tint = TangemTheme.colors.icon.primary1, - contentDescription = null, - ) - } -} - -@Composable -private fun DropdownSelector( - isExpanded: Boolean, - onDismiss: () -> Unit, - onItemClick: (ApproveType) -> Unit, - items: ImmutableList, -) { - DropdownMenu( - expanded = isExpanded, - onDismissRequest = onDismiss, - modifier = Modifier - .wrapContentSize() - .background(TangemTheme.colors.background.secondary), - ) { - items.forEach { item -> - DropdownMenuItem( - onClick = { - onItemClick.invoke(item) - }, - ) { - Text( - text = getTitleForApproveType(approveType = item), - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.body1, - maxLines = 1, - ) - } - } - } -} - -@Composable -private fun FeeItem(fee: String) { - InformationItem( - subtitle = stringResource(id = R.string.send_fee_label), - value = fee, - ) -} - -@Composable -private fun SubtitleItem(subtitle: String, modifier: Modifier = Modifier) { - Text( - modifier = modifier, - text = subtitle, - color = TangemTheme.colors.text.secondary, - style = TangemTheme.typography.body2, - ) -} - -@Composable -private fun getTitleForApproveType(approveType: ApproveType): String = when (approveType) { - ApproveType.LIMITED -> stringResource(id = R.string.swapping_permission_current_transaction) - ApproveType.UNLIMITED -> stringResource(id = R.string.swapping_permission_unlimited) -} - -// region preview - -@Preview -@Composable -private fun Preview_AgreementBottomSheet_InLightTheme() { - TangemTheme(isDark = false) { - SwapPermissionBottomSheetContent(data = previewData) {} - } -} - -@Preview -@Composable -private fun Preview_AgreementBottomSheet_InDarkTheme() { - TangemTheme(isDark = true) { - SwapPermissionBottomSheetContent(data = previewData) {} - } -} - -private val previewData = SwapPermissionState.ReadyForRequest( - currency = "DAI", - amount = "∞", - walletAddress = "", - spenderAddress = "", - fee = TextReference.Str("2,14$"), - approveType = ApproveType.UNLIMITED, - approveButton = ApprovePermissionButton(true) {}, - cancelButton = CancelPermissionButton(true), - onChangeApproveType = { ApproveType.UNLIMITED }, -) +// @Composable +// private fun ApprovalBottomSheetInfo(data: SwapPermissionState.ReadyForRequest) { +// Column( +// modifier = Modifier +// .background(color = TangemTheme.colors.background.primary) +// .fillMaxWidth(), +// horizontalAlignment = Alignment.CenterHorizontally, +// ) { +// AmountItem( +// currency = data.currency, +// approveType = data.approveType, +// onChangeApproveType = data.onChangeApproveType, +// approveItems = data.approveItems, +// ) +// SubtitleItem( +// subtitle = stringResource(id = R.string.swapping_permission_policy_type_footer), +// modifier = Modifier.fillMaxWidth(), +// ) +// SpacerH24() +// DividerBottomSheet() +// FeeItem(fee = data.fee.resolveReference()) +// SubtitleItem( +// subtitle = stringResource(id = R.string.swapping_permission_fee_footer), +// modifier = Modifier.fillMaxWidth(), +// ) +// } +// } +// +// @Composable +// private fun DividerBottomSheet() { +// Divider( +// color = TangemTheme.colors.stroke.primary, +// thickness = TangemTheme.dimens.size0_5, +// ) +// } +// +// @Composable +// private fun InformationItem(subtitle: String, value: String) { +// Row( +// modifier = Modifier +// .fillMaxWidth() +// .padding(vertical = TangemTheme.dimens.spacing16), +// horizontalArrangement = Arrangement.SpaceBetween, +// verticalAlignment = Alignment.CenterVertically, +// ) { +// Text( +// text = subtitle, +// color = TangemTheme.colors.text.primary1, +// style = TangemTheme.typography.subtitle1, +// maxLines = 1, +// ) +// +// MiddleEllipsisText( +// text = value, +// color = TangemTheme.colors.text.tertiary, +// style = TangemTheme.typography.body2, +// modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), +// ) +// } +// } +// +// @Composable +// private fun AmountItem( +// currency: String, +// approveType: ApproveType, +// approveItems: ImmutableList, +// onChangeApproveType: (ApproveType) -> Unit, +// ) { +// var isExpandSelector by remember { +// mutableStateOf(false) +// } +// Row( +// modifier = Modifier +// .fillMaxWidth() +// .padding(vertical = TangemTheme.dimens.spacing16), +// horizontalArrangement = Arrangement.SpaceBetween, +// verticalAlignment = Alignment.CenterVertically, +// ) { +// Text( +// text = stringResource(id = R.string.swapping_permission_rows_amount, currency), +// color = TangemTheme.colors.text.primary1, +// style = TangemTheme.typography.subtitle1, +// maxLines = 1, +// ) +// Box { +// SelectorItem( +// getTitleForApproveType(approveType = approveType), +// ) { +// isExpandSelector = true +// } +// DropdownSelector( +// isExpanded = isExpandSelector, +// onDismiss = { isExpandSelector = false }, +// onItemClick = { approveType -> +// isExpandSelector = false +// onChangeApproveType.invoke(approveType) +// }, +// items = approveItems, +// ) +// } +// } +// } +// +// @Composable +// private fun SelectorItem(title: String, onClick: () -> Unit) { +// Row( +// modifier = Modifier.clickable { onClick() }, +// ) { +// Text( +// text = title, +// color = TangemTheme.colors.text.primary1, +// style = TangemTheme.typography.body1, +// maxLines = 1, +// ) +// Icon( +// painter = painterResource(id = R.drawable.ic_chevron_24), +// tint = TangemTheme.colors.icon.primary1, +// contentDescription = null, +// ) +// } +// } +// +// @Composable +// private fun DropdownSelector( +// isExpanded: Boolean, +// onDismiss: () -> Unit, +// onItemClick: (ApproveType) -> Unit, +// items: ImmutableList, +// ) { +// DropdownMenu( +// expanded = isExpanded, +// onDismissRequest = onDismiss, +// modifier = Modifier +// .wrapContentSize() +// .background(TangemTheme.colors.background.secondary), +// ) { +// items.forEach { item -> +// DropdownMenuItem( +// onClick = { +// onItemClick.invoke(item) +// }, +// ) { +// Text( +// text = getTitleForApproveType(approveType = item), +// color = TangemTheme.colors.text.primary1, +// style = TangemTheme.typography.body1, +// maxLines = 1, +// ) +// } +// } +// } +// } +// +// @Composable +// private fun FeeItem(fee: String) { +// InformationItem( +// subtitle = stringResource(id = R.string.send_fee_label), +// value = fee, +// ) +// } +// +// @Composable +// private fun SubtitleItem(subtitle: String, modifier: Modifier = Modifier) { +// Text( +// modifier = modifier, +// text = subtitle, +// color = TangemTheme.colors.text.secondary, +// style = TangemTheme.typography.body2, +// ) +// } +// +// @Composable +// private fun getTitleForApproveType(approveType: ApproveType): String = when (approveType) { +// ApproveType.LIMITED -> stringResource(id = R.string.swapping_permission_current_transaction) +// ApproveType.UNLIMITED -> stringResource(id = R.string.swapping_permission_unlimited) +// } +// +// // region preview +// +// @Preview +// @Composable +// private fun Preview_AgreementBottomSheet_InLightTheme() { +// TangemTheme(isDark = false) { +// SwapPermissionBottomSheetContent(data = previewData) {} +// } +// } +// +// @Preview +// @Composable +// private fun Preview_AgreementBottomSheet_InDarkTheme() { +// TangemTheme(isDark = true) { +// SwapPermissionBottomSheetContent(data = previewData) {} +// } +// } +// +// private val previewData = SwapPermissionState.ReadyForRequest( +// currency = "DAI", +// amount = "∞", +// walletAddress = "", +// spenderAddress = "", +// fee = TextReference.Str("2,14$"), +// approveType = ApproveType.UNLIMITED, +// approveButton = ApprovePermissionButton(true) {}, +// cancelButton = CancelPermissionButton(true), +// onChangeApproveType = { ApproveType.UNLIMITED }, +// ) //endregion preview \ No newline at end of file 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 850d4434df..692d6a4e57 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,99 +1,44 @@ package com.tangem.feature.swap.ui import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.padding import androidx.compose.material.* +import androidx.compose.material3.* import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.swap.models.SwapPermissionState import com.tangem.feature.swap.models.SwapStateHolder -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch +import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig +import com.tangem.feature.swap.models.states.GivePermissionBottomSheetConfig +import com.tangem.feature.swap.presentation.R -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalLayoutApi::class) @Composable internal fun SwapScreen(stateHolder: SwapStateHolder) { - val coroutineScope = rememberCoroutineScope() - val bottomSheetState = rememberModalBottomSheetState( - initialValue = ModalBottomSheetValue.Hidden, - skipHalfExpanded = true, - ) - BackHandler( - onBack = { - if (bottomSheetState.isVisible) { - hideBottomSheet(coroutineScope, stateHolder, bottomSheetState) - } else { - stateHolder.onBackClicked() - } - }, - ) + BackHandler(onBack = stateHolder.onBackClicked) - LaunchedEffect(bottomSheetState.targetValue) { - if (bottomSheetState.targetValue == ModalBottomSheetValue.Hidden) { - stateHolder.onCancelPermissionBottomSheet.invoke() + Scaffold( + containerColor = TangemTheme.colors.background.secondary, + ) { scaffoldPaddings -> + + SwapScreenContent( + state = stateHolder, + modifier = Modifier.padding(scaffoldPaddings), + ) + + stateHolder.bottomSheetConfig?.let { config -> + when (config.content) { + is GivePermissionBottomSheetConfig -> { + SwapPermissionBottomSheet(config = config) + } + is ChooseProviderBottomSheetConfig -> { + ChooseProviderBottomSheet(config = config) + } + } } } - - ModalBottomSheetLayout( - modifier = Modifier.systemBarsPadding(), - sheetContent = { - if (stateHolder.permissionState is SwapPermissionState.ReadyForRequest) { - SwapPermissionBottomSheetContent( - data = stateHolder.permissionState, - onCancel = { - hideBottomSheet(coroutineScope, stateHolder, bottomSheetState) - }, - ) - } else { - // Required "else" block to prevent compose crash - // always close BS if its empty, cause user should not see this - LaunchedEffect(Unit) { - coroutineScope.launch { bottomSheetState.hide() } - } - Box(modifier = Modifier.fillMaxSize()) - } - }, - sheetState = bottomSheetState, - sheetShape = RoundedCornerShape( - topStart = TangemTheme.dimens.radius16, - topEnd = TangemTheme.dimens.radius16, - ), - sheetElevation = TangemTheme.dimens.elevation24, - scrimColor = TangemTheme.colors.background.secondary.copy(alpha = 0.4f), - content = { - SwapScreenContent( - state = stateHolder, - onPermissionWarningClick = { - val isBottomSheetReady = !bottomSheetState.isVisible && - stateHolder.permissionState is SwapPermissionState.ReadyForRequest - coroutineScope.launch { - if (isBottomSheetReady) { - bottomSheetState.show() - stateHolder.onShowPermissionBottomSheet.invoke() - } else { - bottomSheetState.hide() - } - } - }, - ) - }, - ) -} - -@OptIn(ExperimentalMaterialApi::class) -private fun hideBottomSheet( - coroutineScope: CoroutineScope, - stateHolder: SwapStateHolder, - bottomSheetState: ModalBottomSheetState, -) { - coroutineScope.launch { - stateHolder.onCancelPermissionBottomSheet.invoke() - bottomSheetState.hide() - } } \ No newline at end of file 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 3e87fc6ed8..1ad3351901 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 @@ -10,7 +10,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue @@ -39,30 +38,21 @@ import java.math.BigDecimal @Suppress("LongMethod") @Composable -internal fun SwapScreenContent(state: SwapStateHolder, onPermissionWarningClick: () -> Unit) { +internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modifier) { val keyboard by keyboardAsState() Box( - modifier = Modifier + modifier = modifier .fillMaxSize() .background(color = TangemTheme.colors.background.secondary), ) { - Image( - modifier = Modifier - .size(width = TangemTheme.dimens.size164, height = TangemTheme.dimens.size80) - .align(Alignment.BottomCenter) - .padding(bottom = TangemTheme.dimens.spacing28), - painter = painterResource(id = R.drawable.ill_one_inch_powered), - contentDescription = null, - contentScale = ContentScale.Fit, - ) - Column { AppBarWithBackButton( text = stringResource(R.string.common_swap), onBackClick = state.onBackClicked, iconRes = R.drawable.ic_close_24, ) + Column( modifier = Modifier .fillMaxWidth() @@ -98,7 +88,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, onPermissionWarningClick: }, ) } - MainButton(state = state, onPermissionWarningClick = onPermissionWarningClick) + MainButton(state = state, onPermissionWarningClick = state.onShowPermissionBottomSheet) } } @@ -486,7 +476,7 @@ private val state = SwapStateHolder( @Composable private fun SwapScreenContentPreview() { TangemTheme(isDark = false) { - SwapScreenContent(state = state) {} + SwapScreenContent(state = state, modifier = Modifier) } } 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 cef1abb918..15fa430b17 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 @@ -133,7 +133,6 @@ internal class SwapViewModel @Inject constructor( fun setRouter(router: SwapRouter) { swapRouter = router uiState = uiState.copy( - onBackClicked = router::back, onSelectTokenClick = { router.openScreen(SwapNavScreen.SelectToken) analyticsEventHandler.send(SwapEvents.ChooseTokenScreenOpened) @@ -260,31 +259,7 @@ internal class SwapViewModel @Inject constructor( }, onSuccess = { providersState -> val (provider, state) = updateLoadedQuotes(providersState) - when (state) { - is SwapState.QuotesLoadedState -> { - fillDataState(state.permissionState, state.swapDataModel) - uiState = stateBuilder.createQuotesLoadedState( - uiStateHolder = uiState, - quoteModel = state, - fromToken = fromToken.currency, - swapProvider = provider, - ) { updatedFee -> - dataState = dataState.copy( - selectedFee = updatedFee, - ) - } - } - is SwapState.EmptyAmountState -> { - uiState = stateBuilder.createQuotesEmptyAmountState( - uiStateHolder = uiState, - emptyAmountState = state, - ) - } - is SwapState.SwapError -> { - Timber.e("SwapError when loading quotes ${state.error}") - uiState = stateBuilder.mapError(uiState, state.error) { startLoadingQuotesFromLastState() } - } - } + setupLoadedState(provider, state, fromToken) }, onError = { Timber.e("Error when loading quotes: $it") @@ -293,6 +268,34 @@ internal class SwapViewModel @Inject constructor( ) } + private fun setupLoadedState(provider: SwapProvider, state: SwapState, fromToken: CryptoCurrencyStatus) { + when (state) { + is SwapState.QuotesLoadedState -> { + fillDataState(state.permissionState, state.swapDataModel) + uiState = stateBuilder.createQuotesLoadedState( + uiStateHolder = uiState, + quoteModel = state, + fromToken = fromToken.currency, + swapProvider = provider, + ) { updatedFee -> + dataState = dataState.copy( + selectedFee = updatedFee, + ) + } + } + is SwapState.EmptyAmountState -> { + uiState = stateBuilder.createQuotesEmptyAmountState( + uiStateHolder = uiState, + emptyAmountState = state, + ) + } + is SwapState.SwapError -> { + Timber.e("SwapError when loading quotes ${state.error}") + uiState = stateBuilder.mapError(uiState, state.error) { startLoadingQuotesFromLastState() } + } + } + } + private fun updateLoadedQuotes(state: Map): Pair { val selectedSwapProvider = dataState.selectedProvider ?: state.keys.first() dataState = dataState.copy( @@ -563,11 +566,20 @@ internal class SwapViewModel @Inject constructor( onChangeCardsClicked() analyticsEventHandler.send(SwapEvents.ButtonSwipeClicked) }, - onBackClicked = { onSearchEntered("") }, + onBackClicked = { + val bottomSheet = uiState.bottomSheetConfig + if (bottomSheet != null && bottomSheet.isShow) { + uiState = stateBuilder.dismissBottomSheet(uiState) + } else { + swapRouter.back() + } + onSearchEntered("") + }, onMaxAmountSelected = { onMaxAmountClicked() }, openPermissionBottomSheet = { singleTaskScheduler.cancelTask() analyticsEventHandler.send(SwapEvents.ButtonGivePermissionClicked) + uiState = stateBuilder.showPermissionBottomSheet(uiState) { stateBuilder.dismissBottomSheet(uiState) } }, hidePermissionBottomSheet = { startLoadingQuotesFromLastState() @@ -597,9 +609,38 @@ internal class SwapViewModel @Inject constructor( }, onClickFee = {}, onSelectFeeType = {}, + onProviderClick = { + uiState = stateBuilder.showSelectProviderBottomSheet( + uiState = uiState, + selectedProviderId = it, + providersStates = dataState.lastLoadedSwapStates, + ) { uiState = stateBuilder.dismissBottomSheet(uiState) } + }, + onProviderSelect = { + val provider = findAndSelectProvider(it) + val swapState = dataState.lastLoadedSwapStates[provider] + val fromToken = dataState.fromCryptoCurrency + if (provider != null && swapState != null && fromToken != null) { + setupLoadedState( + provider = provider, + state = swapState, + fromToken = fromToken, + ) + } + }, ) } + private fun findAndSelectProvider(providerId: String): SwapProvider? { + val selectedProvider = dataState.lastLoadedSwapStates.keys.firstOrNull { it.providerId == providerId } + if (selectedProvider != null) { + dataState = dataState.copy( + selectedProvider = selectedProvider, + ) + } + return selectedProvider + } + private fun createSelectedAppCurrencyFlow(): StateFlow { return getSelectedAppCurrencyUseCase() .map { maybeAppCurrency ->