Updated on 2026-08-14
This commit is contained in:
parent
1d00d93dc0
commit
0ff8622215
14 changed files with 813 additions and 467 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -389,27 +389,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated("used in old swap mechanism")
|
||||
private fun selectToToken(
|
||||
initialToken: Currency,
|
||||
tokensInWallet: List<Currency>,
|
||||
loadedTokens: List<Currency>,
|
||||
): 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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<SwapProvider, SwapState>,
|
||||
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<SwapProvider, SwapState>.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
|
||||
|
|
|
|||
|
|
@ -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<ApproveType>,
|
||||
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<ApproveType>,
|
||||
) {
|
||||
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 = {},
|
||||
)
|
||||
|
|
@ -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<ApproveType>,
|
||||
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<ApproveType>,
|
||||
) {
|
||||
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<ApproveType>,
|
||||
// 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<ApproveType>,
|
||||
// ) {
|
||||
// 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
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<SwapProvider, SwapState>): Pair<SwapProvider, SwapState> {
|
||||
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<AppCurrency> {
|
||||
return getSelectedAppCurrencyUseCase()
|
||||
.map { maybeAppCurrency ->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue