diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheets/GiveTxPermisssionBottomSheet.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheets/GiveTxPermisssionBottomSheet.kt new file mode 100644 index 0000000000..e65b7b86f1 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheets/GiveTxPermisssionBottomSheet.kt @@ -0,0 +1,340 @@ +package com.tangem.common.ui.bottomsheets + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.window.PopupProperties +import com.tangem.common.ui.R +import com.tangem.common.ui.bottomsheets.state.* +import com.tangem.core.ui.components.* +import com.tangem.core.ui.components.appbar.AppBarWithAdditionalButtons +import com.tangem.core.ui.components.appbar.models.AdditionalButton +import com.tangem.core.ui.components.atoms.text.EllipsisText +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.containers.FooterContainer +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import kotlinx.collections.immutable.ImmutableList + +@Composable +fun GiveTxPermissionBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet( + config = config, + containerColor = TangemTheme.colors.background.secondary, + ) { content: GiveTxPermissionBottomSheetConfig -> + GiveTxPermissionBottomSheetContent(content = content) + } +} + +@Composable +private fun GiveTxPermissionBottomSheetContent(content: GiveTxPermissionBottomSheetConfig) { + var isPermissionAlertShow by remember { mutableStateOf(false) } + val data = content.data + Column( + modifier = Modifier + .background(color = TangemTheme.colors.background.secondary) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + AppBarWithAdditionalButtons( + text = resourceReference(id = R.string.give_permission_title), + iconColor = TangemTheme.colors.text.tertiary, + endButton = AdditionalButton( + iconRes = R.drawable.ic_information_24, + onIconClicked = { isPermissionAlertShow = true }, + ), + ) + Text( + text = stringResource( + id = R.string.give_permission_subtitle, + data.currency, + ), + color = TangemTheme.colors.text.secondary, + style = TangemTheme.typography.body2, + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing24), + ) + + SpacerH16() + + ApprovalBottomSheetInfo(data) + + SpacerH(height = TangemTheme.dimens.spacing20) + + PrimaryButtonIconEnd( + text = stringResource(id = R.string.common_approve), + iconResId = R.drawable.ic_tangem_24, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing16), + onClick = data.approveButton.onClick, + ) + + SpacerH12() + + SecondaryButton( + text = stringResource(id = R.string.common_cancel), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing16), + 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: GiveTxPermissionState.ReadyForRequest) { + FooterContainer( + footer = stringResource(id = R.string.give_permission_policy_type_footer), + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), + ) { + AmountItem( + currency = data.currency, + approveType = data.approveType, + onChangeApproveType = data.onChangeApproveType, + approveItems = data.approveItems, + ) + } + SpacerH16() + FooterContainer( + footer = stringResource(id = R.string.give_permission_fee_footer), + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), + ) { + FeeItem(fee = data.fee) + } +} + +@Composable +private fun FeeItem(fee: TextReference) { + Row( + modifier = Modifier + .fillMaxWidth() + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.action) + .padding( + top = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing16, + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing16, + ), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(R.string.common_network_fee_title), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + maxLines = 1, + ) + EllipsisText( + text = fee.resolveReference(), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body1, + 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) } + var amountSize by remember { mutableStateOf(IntSize.Zero) } + Box( + modifier = Modifier + .fillMaxWidth() + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.action) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(), + onClick = { isExpandSelector = true }, + ), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .onSizeChanged { amountSize = it } + .padding( + top = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing16, + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing16, + ), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(id = R.string.give_permission_rows_amount, currency), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + maxLines = 1, + ) + when (approveType) { + ApproveType.LIMITED -> { + Text( + text = stringResource(id = R.string.give_permission_current_transaction), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body1, + maxLines = 1, + ) + } + ApproveType.UNLIMITED -> { + Icon( + painter = rememberVectorPainter( + image = ImageVector.vectorResource(id = R.drawable.ic_infinity_24), + ), + tint = TangemTheme.colors.text.tertiary, + contentDescription = null, + modifier = Modifier.size(TangemTheme.dimens.size20), + ) + } + } + } + DropdownSelector( + isExpanded = isExpandSelector, + onDismiss = { isExpandSelector = false }, + onItemClick = { approveType -> + isExpandSelector = false + onChangeApproveType.invoke(approveType) + }, + items = approveItems, + selectedType = approveType, + amountSize = amountSize, + ) + } +} + +@Suppress("LongParameterList") +@Composable +private fun DropdownSelector( + isExpanded: Boolean, + onDismiss: () -> Unit, + onItemClick: (ApproveType) -> Unit, + items: ImmutableList, + selectedType: ApproveType, + amountSize: IntSize, +) { + var dropDownWidth by remember { mutableStateOf(IntSize.Zero) } + val offsetY = amountSize.height.times(-1) + val offsetX = amountSize.width - dropDownWidth.width + + // Workaround to set color and shape of dropdown menu + MaterialTheme( + colorScheme = MaterialTheme.colorScheme.copy(surface = TangemTheme.colors.background.action), + shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(TangemTheme.dimens.radius16)), + ) { + DropdownMenu( + expanded = isExpanded, + onDismissRequest = onDismiss, + properties = PopupProperties(clippingEnabled = false), + offset = with(LocalDensity.current) { + DpOffset(x = offsetX.toDp(), y = offsetY.toDp()) + }, + modifier = Modifier + .wrapContentSize() + .background(TangemTheme.colors.background.action) + .onSizeChanged { dropDownWidth = it }, + ) { + items.forEach { item -> + val color = if (item == selectedType) TangemTheme.colors.icon.accent else Color.Transparent + + DropdownMenuItem( + modifier = Modifier.fillMaxWidth(), + text = { + Row { + Text( + text = when (item) { + ApproveType.LIMITED -> stringResource( + id = R.string.give_permission_current_transaction, + ) + ApproveType.UNLIMITED -> stringResource(id = R.string.give_permission_unlimited) + }, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.body1, + maxLines = 1, + ) + SpacerWMax() + Icon( + painter = rememberVectorPainter( + image = ImageVector.vectorResource(id = R.drawable.ic_check_24), + ), + tint = color, + contentDescription = null, + modifier = Modifier.padding(start = TangemTheme.dimens.size20), + ) + } + }, + onClick = { + onItemClick.invoke(item) + }, + ) + } + } + } +} + +// region preview +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_AgreementBottomSheet() { + TangemThemePreview { + GiveTxPermissionBottomSheetContent(content = previewData) + } +} + +private val previewData = GiveTxPermissionBottomSheetConfig( + data = GiveTxPermissionState.ReadyForRequest( + currency = "DAI", + amount = "1", + 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/common/ui/src/main/java/com/tangem/common/ui/bottomsheets/state/GiveTxPermissionBottomSheetConfig.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheets/state/GiveTxPermissionBottomSheetConfig.kt new file mode 100644 index 0000000000..ad5b970ea9 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheets/state/GiveTxPermissionBottomSheetConfig.kt @@ -0,0 +1,8 @@ +package com.tangem.common.ui.bottomsheets.state + +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent + +data class GiveTxPermissionBottomSheetConfig( + val data: GiveTxPermissionState.ReadyForRequest, + val onCancel: () -> Unit, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheets/state/GiveTxPermissionState.kt similarity index 62% rename from features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt rename to common/ui/src/main/java/com/tangem/common/ui/bottomsheets/state/GiveTxPermissionState.kt index 4192106336..9b07504849 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheets/state/GiveTxPermissionState.kt @@ -1,15 +1,14 @@ -package com.tangem.feature.swap.models +package com.tangem.common.ui.bottomsheets.state import com.tangem.core.ui.extensions.TextReference -import com.tangem.feature.swap.domain.models.domain.SwapApproveType import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -sealed class SwapPermissionState { +sealed class GiveTxPermissionState { - object InProgress : SwapPermissionState() + data object InProgress : GiveTxPermissionState() - object Empty : SwapPermissionState() + data object Empty : GiveTxPermissionState() data class ReadyForRequest( val currency: String, @@ -18,24 +17,17 @@ sealed class SwapPermissionState { val spenderAddress: String, val fee: TextReference, val approveType: ApproveType, - val approveItems: ImmutableList = ApproveType.values().toList().toImmutableList(), + val approveItems: ImmutableList = ApproveType.entries.toImmutableList(), val approveButton: ApprovePermissionButton, val cancelButton: CancelPermissionButton, val onChangeApproveType: (ApproveType) -> Unit, - ) : SwapPermissionState() + ) : GiveTxPermissionState() } enum class ApproveType { LIMITED, UNLIMITED } -fun ApproveType.toDomainApproveType(): SwapApproveType { - return when (this) { - ApproveType.LIMITED -> SwapApproveType.LIMITED - ApproveType.UNLIMITED -> SwapApproveType.UNLIMITED - } -} - data class ApprovePermissionButton( val enabled: Boolean, val loading: Boolean = false, diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 053247f58d..c7913d2da6 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -70,6 +70,7 @@ Доступ запрещен Применить Одобрение + Подтвердить Внимание Баланс: %s Баланс @@ -261,6 +262,13 @@ Обращение в поддержку Обращение в поддержку Tangem Не могу отправить транзакцию + Текущая транзакция + Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена. + Укажите лимит доступа к выбранному токену + Количество %s + Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s + Дать разрешение + Безлимитно Купить Сканировать Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции @@ -388,6 +396,7 @@ Активация карты Резервная карта #%d Нет резервных карт + Уведомления Добавлена ​​одна резервная карта Подготовьте свою карту Добавлены две резервные карты @@ -581,17 +590,9 @@ Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен. Подтвердить Вы отправляете - Дать разрешение Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму. Недостаточно средств - Подтвердить - Текущая транзакция - Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена. Дать разрешение - Укажите лимит доступа к выбранному токену - Количество %s - Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s - Безлимитно В процессе Обменять Вы получите diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index e0b8c69381..28f111ab8b 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -277,13 +277,9 @@ 兼容Web3.0 批准被視為所有去中心化交易所的行業標準,並保護您的錢包在未經您許可的情況下不被智能合約訪問。按照設計,智能合約無法訪問您的代幣,除非您從您的終端批准訪問。通過“解鎖”您的代幣,您將獲得 1inch 智能合約使用您的資產的權限。網絡的礦工將獲得Gas Fee(由您支付)作為補償,以在區塊鏈上記錄此操作。一旦獲得許可,您就可以交易您的代幣。 批准 - 賦予權限 在此代幣交換的數量將對價格產生重大影響,並降低您收到的數量 餘額不足 - 允許 賦予權限 - 數量 %s - 要繼續,您需要允許 1inch 智能合約使用您的 %s 進行中 交易 選擇代幣 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index e7228a19a8..28d7f3aefc 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -71,6 +71,7 @@ Allow Apply Approval + Approve Attention Balance: %s Balance @@ -264,6 +265,13 @@ Feedback Tangem feedback Can\'t send a transaction + Current transaction + The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. + Specify the approve limit for the selected token + Amount %s + To continue, grant 1inch smart contracts permission to use your %s + Give Permission + Unlimited Order card Scan card To change the access code tap the card as shown above and do not remove until the end of the operation @@ -397,6 +405,7 @@ Activating card Backup card #%d No backup cards + Notifications One backup card added Prepare your card Two backup cards added @@ -607,17 +616,9 @@ All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval. Approve You swap - Give Permission Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome. Insufficient funds - Approve - Current transaction - The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. Give Permission - Specify the approve limit for the selected token - Amount %s - To continue, grant 1inch smart contracts permission to use your %s - Unlimited In progress Swap You receive @@ -671,7 +672,7 @@ Use %s or scan a card to have an access to your wallet Stay up to date with the latest features and news Be able to get important market notifications - Would you like to use
Push-notifications? + Would you like to use\nPush-notifications? Add new wallet Are you sure you want to delete this wallet? An error has occurred, please scan your card to log in diff --git a/core/ui/src/main/res/drawable/ic_infinity_24.xml b/core/ui/src/main/res/drawable/ic_infinity_24.xml new file mode 100644 index 0000000000..cf0d1b2c54 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_infinity_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index f82d33b863..c41603a025 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation(projects.core.utils) implementation(projects.core.ui) implementation(projects.common.routing) + implementation(projects.common.ui) /** Domain modules **/ implementation(projects.domain.appCurrency) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt index 2fd8fa08ac..2c8c11fb70 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt @@ -1,9 +1,9 @@ package com.tangem.feature.swap.analytics +import com.tangem.common.ui.bottomsheets.state.ApproveType import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.feature.swap.domain.models.domain.SwapProvider import com.tangem.feature.swap.domain.models.ui.FeeType -import com.tangem.feature.swap.models.ApproveType private const val SWAP_CATEGORY = "Swap" 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 cd22de2a3b..e3d7c3d5c8 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 @@ -3,6 +3,7 @@ package com.tangem.feature.swap.models import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.ui.text.input.TextFieldValue +import com.tangem.common.ui.bottomsheets.state.GiveTxPermissionState import com.tangem.core.ui.R import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.notifications.NotificationConfig @@ -22,7 +23,7 @@ data class SwapStateHolder( val providerState: ProviderState, val fee: FeeItemState = FeeItemState.Empty, - val permissionState: SwapPermissionState = SwapPermissionState.Empty, + val permissionState: GiveTxPermissionState = GiveTxPermissionState.Empty, val priceImpact: PriceImpact, val successState: SwapSuccessStateHolder? = null, 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 6bbee12717..c0c890750e 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 @@ -1,5 +1,6 @@ package com.tangem.feature.swap.models +import com.tangem.common.ui.bottomsheets.state.ApproveType import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.ui.TxFee 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 deleted file mode 100644 index 9a8bd7a519..0000000000 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/GivePermissionBottomSheetConfig.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.feature.swap.models.states - -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.feature.swap.models.SwapPermissionState - -data 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/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index c902406819..02a25af845 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 @@ -2,6 +2,7 @@ package com.tangem.feature.swap.ui import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue +import com.tangem.common.ui.bottomsheets.state.* import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.components.notifications.NotificationConfig @@ -649,7 +650,7 @@ internal class StateBuilder( ), receiveCardData = receiveCardData, warnings = warnings, - permissionState = SwapPermissionState.Empty, + permissionState = GiveTxPermissionState.Empty, fee = FeeItemState.Empty, swapButton = SwapButton( enabled = false, @@ -906,7 +907,7 @@ internal class StateBuilder( } fun updateApproveType(uiState: SwapStateHolder, approveType: ApproveType): SwapStateHolder { - val config = uiState.bottomSheetConfig?.content as? GivePermissionBottomSheetConfig + val config = uiState.bottomSheetConfig?.content as? GiveTxPermissionBottomSheetConfig return if (config != null) { uiState.copy( bottomSheetConfig = uiState.bottomSheetConfig.copy( @@ -980,7 +981,7 @@ internal class StateBuilder( ), ) return uiState.copy( - permissionState = SwapPermissionState.InProgress, + permissionState = GiveTxPermissionState.InProgress, warnings = warnings, ) } @@ -1140,27 +1141,27 @@ internal class StateBuilder( } private fun convertPermissionState( - lastPermissionState: SwapPermissionState, + lastPermissionState: GiveTxPermissionState, permissionDataState: PermissionDataState, onGivePermissionClick: () -> Unit, onChangeApproveType: (ApproveType) -> Unit, - ): SwapPermissionState { - val approveType = if (lastPermissionState is SwapPermissionState.ReadyForRequest) { + ): GiveTxPermissionState { + val approveType = if (lastPermissionState is GiveTxPermissionState.ReadyForRequest) { lastPermissionState.approveType } else { ApproveType.UNLIMITED } return when (permissionDataState) { - PermissionDataState.Empty -> SwapPermissionState.Empty - PermissionDataState.PermissionFailed -> SwapPermissionState.Empty - PermissionDataState.PermissionLoading -> SwapPermissionState.InProgress + PermissionDataState.Empty -> GiveTxPermissionState.Empty + PermissionDataState.PermissionFailed -> GiveTxPermissionState.Empty + PermissionDataState.PermissionLoading -> GiveTxPermissionState.InProgress is PermissionDataState.PermissionReadyForRequest -> { val permissionFee = when (val fee = permissionDataState.requestApproveData.fee) { TxFeeState.Empty -> error("Fee shouldn't be empty") is TxFeeState.MultipleFeeState -> fee.priorityFee is TxFeeState.SingleFeeState -> fee.fee } - SwapPermissionState.ReadyForRequest( + GiveTxPermissionState.ReadyForRequest( currency = permissionDataState.currency, amount = permissionDataState.amount, approveType = approveType, @@ -1193,8 +1194,8 @@ internal class StateBuilder( fun showPermissionBottomSheet(uiState: SwapStateHolder, onDismiss: () -> Unit): SwapStateHolder { val permissionState = uiState.permissionState - if (permissionState is SwapPermissionState.ReadyForRequest) { - val config = GivePermissionBottomSheetConfig( + if (permissionState is GiveTxPermissionState.ReadyForRequest) { + val config = GiveTxPermissionBottomSheetConfig( data = permissionState, onCancel = onDismiss, ) @@ -1402,7 +1403,7 @@ internal class StateBuilder( return NotificationConfig( title = resourceReference(R.string.express_provider_permission_needed), subtitle = resourceReference( - id = R.string.swapping_permission_subheader, + id = R.string.give_permission_subtitle, formatArgs = wrappedList(fromTokenSymbol), ), iconResId = R.drawable.ic_locked_24, 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 deleted file mode 100644 index 8ac175588a..0000000000 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt +++ /dev/null @@ -1,317 +0,0 @@ -package com.tangem.feature.swap.ui - -import android.content.res.Configuration -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.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.core.ui.res.TangemThemePreview -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, - ) { - 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.common_network_fee_title), - 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 -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview_AgreementBottomSheet() { - TangemThemePreview { - 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/SwapScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt index ff34b02557..9ae4b97bca 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 @@ -6,13 +6,14 @@ import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import com.tangem.common.ui.bottomsheets.GiveTxPermissionBottomSheet +import com.tangem.common.ui.bottomsheets.state.GiveTxPermissionBottomSheetConfig import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.WindowInsetsZero import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.models.states.ChooseFeeBottomSheetConfig import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig -import com.tangem.feature.swap.models.states.GivePermissionBottomSheetConfig import com.tangem.feature.swap.models.states.WebViewBottomSheetConfig import com.tangem.feature.swap.presentation.R @@ -40,8 +41,8 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) { stateHolder.bottomSheetConfig?.let { config -> when (config.content) { - is GivePermissionBottomSheetConfig -> { - SwapPermissionBottomSheet(config = config) + is GiveTxPermissionBottomSheetConfig -> { + GiveTxPermissionBottomSheet(config = config) } is ChooseProviderBottomSheetConfig -> { ChooseProviderBottomSheet(config = config) 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 a0c5919e82..582ba1f779 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 @@ -23,6 +23,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.constraintlayout.compose.ConstraintLayout +import com.tangem.common.ui.bottomsheets.state.GiveTxPermissionState import com.tangem.core.ui.components.* import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.notifications.NotificationConfig @@ -414,7 +415,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U state.warnings.any { it is SwapWarning.PermissionNeeded } -> { PrimaryButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = R.string.swapping_give_permission), + text = stringResource(id = R.string.give_permission_title), enabled = true, onClick = onPermissionWarningClick, ) @@ -495,7 +496,7 @@ private val state = SwapStateHolder( onRefresh = {}, onBackClicked = {}, onChangeCardsClicked = {}, - permissionState = SwapPermissionState.InProgress, + permissionState = GiveTxPermissionState.InProgress, blockchainId = "POLYGON", providerState = ProviderState.Loading(), priceImpact = PriceImpact.Empty(), diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt index aada4b7d2b..969fadc27a 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.viewmodels +import com.tangem.common.ui.bottomsheets.state.ApproveType import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.swap.domain.models.domain.SwapDataModel import com.tangem.feature.swap.domain.models.domain.SwapProvider @@ -7,7 +8,6 @@ import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData import com.tangem.feature.swap.domain.models.ui.SwapState import com.tangem.feature.swap.domain.models.ui.TokensDataStateExpress import com.tangem.feature.swap.domain.models.ui.TxFee -import com.tangem.feature.swap.models.ApproveType data class SwapProcessDataState( // Initial network id 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 692cb6ea7a..398a248ec5 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 @@ -9,6 +9,7 @@ import arrow.core.Either import arrow.core.getOrElse import com.tangem.common.routing.AppRoute import com.tangem.common.routing.bundle.unbundle +import com.tangem.common.ui.bottomsheets.state.ApproveType import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic @@ -73,7 +74,7 @@ internal class SwapViewModel @Inject constructor( ) : ViewModel(), DefaultLifecycleObserver { private val initialCryptoCurrency: CryptoCurrency = savedStateHandle.get(AppRoute.Swap.CURRENCY_BUNDLE_KEY) - ?.let { it.unbundle(CryptoCurrency.serializer()) } + ?.unbundle(CryptoCurrency.serializer()) ?: error("no expected parameter CryptoCurrency found`") private lateinit var initialCryptoCurrencyStatus: CryptoCurrencyStatus @@ -1173,6 +1174,13 @@ internal class SwapViewModel @Inject constructor( ) } + private fun ApproveType.toDomainApproveType(): SwapApproveType { + return when (this) { + ApproveType.LIMITED -> SwapApproveType.LIMITED + ApproveType.UNLIMITED -> SwapApproveType.UNLIMITED + } + } + private companion object { const val loggingTag = "SwapViewModel" const val INITIAL_AMOUNT = ""