From 646fe2d3494f77d26261b478b0445b126415a2eb Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Oct 2025 14:18:28 +0200 Subject: [PATCH] Updated on 2026-08-14 --- .../GiveTxPermisssionBottomSheet.kt | 22 ++++++++- .../permission/state/GiveTxPermissionState.kt | 1 + .../main/java/com/tangem/utils/TangemLinks.kt | 2 + .../presentation/model/StakingClickIntents.kt | 2 + .../impl/presentation/model/StakingModel.kt | 7 +++ .../state/stub/StakingClickIntentsStub.kt | 2 + .../ShowApprovalBottomSheetTransformer.kt | 1 + .../tangem/feature/swap/model/SwapModel.kt | 4 ++ .../swap/model/SwapNotificationsFactory.kt | 1 + .../tangem/feature/swap/models/UiActions.kt | 1 + .../swap/models/states/SwapNotificationUM.kt | 5 ++ .../tangem/feature/swap/ui/StateBuilder.kt | 7 ++- .../feature/swap/ui/SwapScreenContent.kt | 49 ++++++++----------- 13 files changed, 72 insertions(+), 32 deletions(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt index 0f7a278f3e..48d962e377 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt @@ -17,7 +17,9 @@ import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withLink import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.IntSize @@ -26,8 +28,8 @@ import com.tangem.common.ui.R import com.tangem.common.ui.bottomsheet.permission.state.* import com.tangem.core.ui.components.* import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM -import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet import com.tangem.core.ui.components.containers.FooterContainer import com.tangem.core.ui.components.inputrow.InputRowDefault import com.tangem.core.ui.extensions.* @@ -114,7 +116,22 @@ private fun GiveTxPermissionBottomSheetContent(content: GiveTxPermissionBottomSh @Composable private fun ApprovalBottomSheetInfo(data: GiveTxPermissionState.ReadyForRequest) { FooterContainer( - footer = resourceReference(R.string.give_permission_policy_type_footer), + footer = annotatedReference { + append(stringResourceSafe(R.string.swap_approve_description)) + append(" ") + withLink( + link = LinkAnnotation.Clickable( + tag = "APPROVE_TAG", + linkInteractionListener = { data.onOpenLearnMoreAboutApproveClick() }, + ), + block = { + appendColored( + text = stringResourceSafe(R.string.common_learn_more), + color = TangemTheme.colors.text.accent, + ) + }, + ) + }, modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), ) { AmountItem( @@ -318,6 +335,7 @@ private val previewData = GiveTxPermissionBottomSheetConfig( subtitle = resourceReference(R.string.give_permission_staking_subtitle, wrappedList("1")), dialogText = resourceReference(R.string.give_permission_staking_footer), footerText = resourceReference(R.string.swap_give_permission_fee_footer), + onOpenLearnMoreAboutApproveClick = {}, ), walletInteractionIcon = R.drawable.ic_tangem_24, onCancel = {}, diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt index bc870a57e2..bcf8f4064e 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt @@ -26,6 +26,7 @@ sealed class GiveTxPermissionState { val approveButton: ApprovePermissionButton, val cancelButton: CancelPermissionButton, val onChangeApproveType: ((ApproveType) -> Unit)? = null, + val onOpenLearnMoreAboutApproveClick: () -> Unit, ) : GiveTxPermissionState() fun GiveTxPermissionState.getApproveTypeOrNull(): ApproveType? { diff --git a/core/utils/src/main/java/com/tangem/utils/TangemLinks.kt b/core/utils/src/main/java/com/tangem/utils/TangemLinks.kt index 3a1c60869e..285b5859d2 100644 --- a/core/utils/src/main/java/com/tangem/utils/TangemLinks.kt +++ b/core/utils/src/main/java/com/tangem/utils/TangemLinks.kt @@ -18,4 +18,6 @@ object TangemLinks { append("/blog/post/what-is-a-transaction-fee-and-why-do-we-need-it/") } } + + const val RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP = "https://tangem.com/en/blog/post/give-revoke-permission/" } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt index 23a8e8b53e..28507abd9f 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt @@ -67,4 +67,6 @@ internal interface StakingClickIntents : AmountScreenClickIntents { fun openTokenDetails(cryptoCurrency: CryptoCurrency) fun showPrimaryClickAlert() + + fun onOpenLearnMoreAboutApproveClick() } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index df70e2bf63..df672cadf7 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -16,6 +16,7 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.share.ShareManager +import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.haptic.TangemHapticEffect @@ -73,6 +74,7 @@ import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalcula import com.tangem.features.staking.impl.presentation.state.utils.isSingleAction import com.tangem.features.staking.impl.presentation.state.utils.withStubUnstakeAction import com.tangem.utils.Provider +import com.tangem.utils.TangemLinks.RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP import com.tangem.utils.coroutines.* import com.tangem.utils.extensions.isSingleItem import com.tangem.utils.extensions.orZero @@ -123,6 +125,7 @@ internal class StakingModel @Inject constructor( private val getActionRequirementAmountUseCase: GetActionRequirementAmountUseCase, private val paramsInterceptorHolder: ParamsInterceptorHolder, private val shareManager: ShareManager, + private val urlOpener: UrlOpener, @DelayedWork private val coroutineScope: CoroutineScope, private val innerRouter: InnerStakingRouter, appRouter: AppRouter, @@ -927,6 +930,10 @@ internal class StakingModel @Inject constructor( ) } + override fun onOpenLearnMoreAboutApproveClick() { + urlOpener.openUrl(RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP) + } + private suspend fun setupApprovalNeeded() { val approval = StakingIntegrationID.create(currencyId = cryptoCurrencyStatus.currency.id)?.approval ?: StakingApproval.Empty diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt index 68eed82fb9..d1ed5d5c98 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt @@ -76,4 +76,6 @@ internal object StakingClickIntentsStub : StakingClickIntents { override fun onActiveStakeAnalytic() {} override fun showPrimaryClickAlert() {} + + override fun onOpenLearnMoreAboutApproveClick() {} } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt index 8b1ede3cbe..f0b2ca6298 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt @@ -77,6 +77,7 @@ internal class ShowApprovalBottomSheetTransformer( dialogText = resourceReference(R.string.give_permission_staking_footer), footerText = resourceReference(R.string.staking_give_permission_fee_footer), onChangeApproveType = prevState.clickIntents::onApproveTypeChange, + onOpenLearnMoreAboutApproveClick = {}, ), walletInteractionIcon = walletInterationIcon(userWallet), onCancel = onDismiss, diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index fe64745c2b..f7cf259a23 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -63,6 +63,7 @@ import com.tangem.feature.swap.ui.StateBuilder import com.tangem.feature.swap.utils.formatToUIRepresentation import com.tangem.features.swap.SwapComponent import com.tangem.utils.Provider +import com.tangem.utils.TangemLinks.RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP import com.tangem.utils.coroutines.* import com.tangem.utils.isNullOrZero import kotlinx.coroutines.NonCancellable @@ -1164,6 +1165,9 @@ internal class SwapModel @Inject constructor( modelScope.launch { shouldShowStoriesUseCase.neverToShow(StoryContentIds.STORY_FIRST_TIME_SWAP.id) } swapRouter.openScreen(SwapNavScreen.Main) }, + onOpenLearnMoreAboutApproveClick = { + urlOpener.openUrl(RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP) + }, ) } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapNotificationsFactory.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapNotificationsFactory.kt index 36018d5189..cb90d135f8 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapNotificationsFactory.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapNotificationsFactory.kt @@ -243,6 +243,7 @@ internal class SwapNotificationsFactory( SwapNotificationUM.Info.PermissionNeeded( providerName = fromToken.symbol, fromTokenSymbol = providerName, + onApproveClick = actions.openPermissionBottomSheet, ), ) } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/models/UiActions.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/models/UiActions.kt index 0969d9b54b..25c2225451 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/models/UiActions.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/models/UiActions.kt @@ -32,4 +32,5 @@ data class UiActions( val onSuccess: () -> Unit, val onLinkClick: (String) -> Unit, val onReceiveCardWarningClick: () -> Unit, + val onOpenLearnMoreAboutApproveClick: () -> Unit, ) \ No newline at end of file diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/models/states/SwapNotificationUM.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/models/states/SwapNotificationUM.kt index a5f0f1a251..317e905564 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/models/states/SwapNotificationUM.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/models/states/SwapNotificationUM.kt @@ -199,6 +199,7 @@ internal object SwapNotificationUM { data class PermissionNeeded( val providerName: String, val fromTokenSymbol: String, + val onApproveClick: () -> Unit, ) : Info( title = resourceReference(R.string.express_provider_permission_needed), subtitle = resourceReference( @@ -206,6 +207,10 @@ internal object SwapNotificationUM { formatArgs = wrappedList(providerName, fromTokenSymbol), ), iconResId = R.drawable.ic_locked_24, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = TextReference.Res(R.string.common_approve), + onClick = onApproveClick, + ), ) } } \ No newline at end of file diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 039cefd24b..a21003f5f4 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -347,6 +347,7 @@ internal class StateBuilder( providerName = swapProvider.name, onGivePermissionClick = actions.onGivePermissionClick, onChangeApproveType = actions.onChangeApproveType, + onOpenLearnMoreAboutApproveClick = actions.onOpenLearnMoreAboutApproveClick, ), fee = feeState, swapButton = SwapButton( @@ -411,7 +412,8 @@ internal class StateBuilder( it is SwapNotificationUM.Error || it is NotificationUM.Error || it is SwapNotificationUM.Warning.ExpressError || it is SwapNotificationUM.Warning.ExpressGeneralError || it is SwapNotificationUM.Warning.NoAvailableTokensToSwap || - it is SwapNotificationUM.Warning.NeedReserveToCreateAccount + it is SwapNotificationUM.Warning.NeedReserveToCreateAccount || + it is SwapNotificationUM.Info.PermissionNeeded } } @@ -934,12 +936,14 @@ internal class StateBuilder( ) } + @Suppress("LongParameterList") private fun convertPermissionState( lastPermissionState: GiveTxPermissionState, permissionDataState: PermissionDataState, providerName: String, onGivePermissionClick: () -> Unit, onChangeApproveType: (ApproveType) -> Unit, + onOpenLearnMoreAboutApproveClick: () -> Unit, ): GiveTxPermissionState { val approveType = if (lastPermissionState is GiveTxPermissionState.ReadyForRequest) { lastPermissionState.approveType @@ -977,6 +981,7 @@ internal class StateBuilder( ), dialogText = resourceReference(R.string.swapping_approve_information_text), footerText = resourceReference(R.string.swap_give_permission_fee_footer), + onOpenLearnMoreAboutApproveClick = onOpenLearnMoreAboutApproveClick, ) } } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 8fe8424317..2fe84bb466 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -78,7 +78,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi if (state.notifications.isNotEmpty()) SwapNotifications(notifications = state.notifications) - MainButton(state = state, onPermissionWarningClick = state.onShowPermissionBottomSheet) + SpacerHMax() if (state.tosState != null && state.providerState !is ProviderState.Empty) { ProviderTos( @@ -87,6 +87,8 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi .padding(top = TangemTheme.dimens.spacing16), ) } + + MainButton(state = state) } if (state.shouldShowMaxAmount && keyboard is Keyboard.Opened) { @@ -359,34 +361,22 @@ private fun SwapNotifications(notifications: List) { } @Composable -private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> Unit) { - // order is important - when { - state.isInsufficientFunds -> { - PrimaryButton( - modifier = Modifier.fillMaxWidth(), - text = stringResourceSafe(id = R.string.swapping_insufficient_funds), - enabled = false, - onClick = state.swapButton.onClick, - ) - } - state.notifications.any { it is SwapNotificationUM.Info.PermissionNeeded } -> { - PrimaryButton( - modifier = Modifier.fillMaxWidth(), - text = stringResourceSafe(id = R.string.give_permission_title), - enabled = true, - onClick = onPermissionWarningClick, - ) - } - else -> { - PrimaryButtonIconEnd( - modifier = Modifier.fillMaxWidth(), - text = stringResourceSafe(id = R.string.swapping_swap_action), - iconResId = state.swapButton.walletInteractionIcon, - enabled = state.swapButton.enabled, - onClick = state.swapButton.onClick, - ) - } +private fun MainButton(state: SwapStateHolder) { + if (state.isInsufficientFunds) { + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = stringResourceSafe(id = R.string.swapping_insufficient_funds), + enabled = false, + onClick = state.swapButton.onClick, + ) + } else { + PrimaryButtonIconEnd( + modifier = Modifier.fillMaxWidth(), + text = stringResourceSafe(id = R.string.swapping_swap_action), + iconResId = state.swapButton.walletInteractionIcon, + enabled = state.swapButton.enabled, + onClick = state.swapButton.onClick, + ) } } @@ -438,6 +428,7 @@ private val state = SwapStateHolder( SwapNotificationUM.Info.PermissionNeeded( providerName = "Provider", fromTokenSymbol = "POL", + onApproveClick = {}, ), SwapNotificationUM.Warning.NoAvailableTokensToSwap("POLYGON"), ),