From 410ecb6fc0deef4bdf6d9af24535f10ec2fd2ed5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 7 Jul 2025 18:10:46 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../send/v2/api/FeeSelectorBlockComponent.kt | 10 +- .../send/v2/api/FeeSelectorComponent.kt | 10 +- .../send/v2/api/params/FeeSelectorParams.kt | 12 +- .../DefaultFeeSelectorBlockComponent.kt | 200 +++++------------- .../DefaultFeeSelectorComponent.kt | 11 +- .../v2/feeselector/model/FeeSelectorModel.kt | 21 +- .../feeselector/ui/FeeSelectorBlockContent.kt | 153 ++++++++++++++ .../ui/FeeSelectorModalBottomSheet.kt | 56 ++--- .../components/common/WcNavigationUtils.kt | 2 + .../send/WcSendTransactionComponent.kt | 3 +- .../entity/common/WcCommonTransactionModel.kt | 2 + .../transaction/model/WcAddNetworkModel.kt | 4 + .../model/WcSendTransactionModel.kt | 17 ++ .../model/WcSignTransactionModel.kt | 5 + 14 files changed, 311 insertions(+), 195 deletions(-) create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorBlockContent.kt diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/FeeSelectorBlockComponent.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/FeeSelectorBlockComponent.kt index 5d2744f758..dfa21df77c 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/FeeSelectorBlockComponent.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/FeeSelectorBlockComponent.kt @@ -1,6 +1,6 @@ package com.tangem.features.send.v2.api -import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.features.send.v2.api.entity.FeeSelectorUM import com.tangem.features.send.v2.api.params.FeeSelectorParams @@ -9,5 +9,11 @@ interface FeeSelectorBlockComponent : ComposableContentComponent { fun updateState(feeSelectorUM: FeeSelectorUM) - interface Factory : ComponentFactory + interface Factory { + fun create( + context: AppComponentContext, + params: FeeSelectorParams.FeeSelectorBlockParams, + onResult: (feeSelectorUM: FeeSelectorUM) -> Unit, + ): FeeSelectorBlockComponent + } } \ No newline at end of file diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/FeeSelectorComponent.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/FeeSelectorComponent.kt index 984f411097..ff1e8703c9 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/FeeSelectorComponent.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/FeeSelectorComponent.kt @@ -1,9 +1,15 @@ package com.tangem.features.send.v2.api -import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.features.send.v2.api.params.FeeSelectorParams interface FeeSelectorComponent : ComposableBottomSheetComponent { - interface Factory : ComponentFactory + interface Factory { + fun create( + context: AppComponentContext, + params: FeeSelectorParams.FeeSelectorDetailsParams, + onDismiss: () -> Unit, + ): FeeSelectorComponent + } } \ No newline at end of file diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/params/FeeSelectorParams.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/params/FeeSelectorParams.kt index aac9799e08..db5fa2513b 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/params/FeeSelectorParams.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/params/FeeSelectorParams.kt @@ -13,27 +13,33 @@ sealed class FeeSelectorParams { abstract val state: FeeSelectorUM abstract val onLoadFee: suspend () -> Either abstract val cryptoCurrencyStatus: CryptoCurrencyStatus - abstract val callback: FeeSelectorModelCallback abstract val suggestedFeeState: SuggestedFeeState + abstract val feeDisplaySource: FeeDisplaySource data class FeeSelectorBlockParams( override val state: FeeSelectorUM, override val onLoadFee: suspend () -> Either, override val cryptoCurrencyStatus: CryptoCurrencyStatus, - override val callback: FeeSelectorModelCallback, override val suggestedFeeState: SuggestedFeeState, + override val feeDisplaySource: FeeDisplaySource, ) : FeeSelectorParams() data class FeeSelectorDetailsParams( override val state: FeeSelectorUM, override val onLoadFee: suspend () -> Either, override val cryptoCurrencyStatus: CryptoCurrencyStatus, - override val callback: FeeSelectorModelCallback, override val suggestedFeeState: SuggestedFeeState, + override val feeDisplaySource: FeeDisplaySource, + val callback: FeeSelectorModelCallback, ) : FeeSelectorParams() sealed class SuggestedFeeState { data object None : SuggestedFeeState() data class Suggestion(val title: TextReference, val fee: Fee) : SuggestedFeeState() } + + enum class FeeDisplaySource { + Screen, + BottomSheet, + } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/DefaultFeeSelectorBlockComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/DefaultFeeSelectorBlockComponent.kt index 8e9135b92a..0a05f94348 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/DefaultFeeSelectorBlockComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/DefaultFeeSelectorBlockComponent.kt @@ -1,63 +1,65 @@ package com.tangem.features.send.v2.feeselector -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material3.Icon -import androidx.compose.material3.Text +import androidx.compose.foundation.clickable import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.common.ui.amountScreen.utils.getFiatString +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.decompose.router.slot.activate +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.core.ui.components.SpacerWMax -import com.tangem.core.ui.components.TextShimmer -import com.tangem.core.ui.components.atoms.text.EllipsisText -import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.EMPTY_BALANCE_SIGN -import com.tangem.core.ui.format.bigdecimal.crypto -import com.tangem.core.ui.format.bigdecimal.fee -import com.tangem.core.ui.format.bigdecimal.format -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.core.ui.extensions.conditional import com.tangem.features.send.v2.api.FeeSelectorBlockComponent -import com.tangem.features.send.v2.api.entity.FeeFiatRateUM -import com.tangem.features.send.v2.api.entity.FeeItem +import com.tangem.features.send.v2.api.FeeSelectorComponent import com.tangem.features.send.v2.api.entity.FeeSelectorUM import com.tangem.features.send.v2.api.params.FeeSelectorParams import com.tangem.features.send.v2.feeselector.model.FeeSelectorModel -import com.tangem.features.send.v2.impl.R +import com.tangem.features.send.v2.feeselector.ui.FeeSelectorBlockContent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import java.math.BigDecimal +import kotlinx.serialization.builtins.serializer internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, - @Assisted params: FeeSelectorParams.FeeSelectorBlockParams, + @Assisted private val params: FeeSelectorParams.FeeSelectorBlockParams, + @Assisted onResult: (feeSelectorUM: FeeSelectorUM) -> Unit, + private val feeSelectorComponentFactory: FeeSelectorComponent.Factory, ) : FeeSelectorBlockComponent, AppComponentContext by appComponentContext { private val model: FeeSelectorModel = getOrCreateModel(params = params) + private val bottomSheetSlot = childSlot( + source = model.feeSelectorBottomSheet, + serializer = Unit.serializer(), + handleBackButton = false, + childFactory = { _, componentContext -> + feeSelectorComponentFactory.create( + context = childByContext(componentContext), + params = FeeSelectorParams.FeeSelectorDetailsParams( + state = model.uiState.value, + onLoadFee = params.onLoadFee, + cryptoCurrencyStatus = params.cryptoCurrencyStatus, + callback = model, + suggestedFeeState = FeeSelectorParams.SuggestedFeeState.None, + feeDisplaySource = FeeSelectorParams.FeeDisplaySource.Screen, + ), + onDismiss = { + model.feeSelectorBottomSheet.dismiss() + }, + ) + }, + ) + init { model.uiState - .onEach(params.callback::onFeeResult) + .onEach(onResult) .launchIn(componentScope) } @@ -68,7 +70,18 @@ internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor( @Composable override fun Content(modifier: Modifier) { val state by model.uiState.collectAsStateWithLifecycle() - FeeSelectorBlockContent(modifier = modifier, state = state) + val bottomSheet by bottomSheetSlot.subscribeAsState() + + FeeSelectorBlockContent( + state = state, + modifier = modifier + .conditional(params.feeDisplaySource == FeeSelectorParams.FeeDisplaySource.Screen) { + Modifier.clickable { + model.feeSelectorBottomSheet.activate(Unit) + } + }, + ) + bottomSheet.child?.instance?.BottomSheet() } @AssistedFactory @@ -76,122 +89,7 @@ internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor( override fun create( context: AppComponentContext, params: FeeSelectorParams.FeeSelectorBlockParams, + onResult: (feeSelectorUM: FeeSelectorUM) -> Unit, ): DefaultFeeSelectorBlockComponent } -} - -@Composable -private fun FeeSelectorBlockContent(state: FeeSelectorUM, modifier: Modifier = Modifier) { - Row( - modifier = modifier - .background(TangemTheme.colors.background.action) - .padding(12.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - modifier = Modifier.size(24.dp), - painter = painterResource(R.drawable.ic_fee_new_24), - contentDescription = null, - tint = TangemTheme.colors.icon.accent, - ) - Text( - modifier = Modifier.padding(start = TangemTheme.dimens.spacing4), - text = stringResourceSafe(R.string.common_network_fee_title), - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.primary1, - ) - Icon( - modifier = Modifier - .padding(start = TangemTheme.dimens.spacing6) - .size(TangemTheme.dimens.size16), - painter = painterResource(id = R.drawable.ic_token_info_24), - contentDescription = null, - tint = TangemTheme.colors.icon.informative, - ) - SpacerWMax() - when (state) { - is FeeSelectorUM.Content -> FeeContent(state) - is FeeSelectorUM.Loading -> FeeLoading() - is FeeSelectorUM.Error -> FeeError() - } - } -} - -@Composable -private fun RowScope.FeeError() { - Text( - text = EMPTY_BALANCE_SIGN, - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.body2, - ) -} - -@Composable -private fun RowScope.FeeLoading() { - TextShimmer( - radius = TangemTheme.dimens.radius3, - style = TangemTheme.typography.body1, - modifier = Modifier.width(width = TangemTheme.dimens.size90), - ) -} - -@Composable -private fun RowScope.FeeContent(state: FeeSelectorUM.Content) { - val fiatRate = state.feeFiatRateUM - EllipsisText( - text = if (fiatRate != null) { - getFiatString( - value = state.selectedFeeItem.fee.amount.value, - rate = fiatRate.rate, - appCurrency = fiatRate.appCurrency, - approximate = state.isFeeApproximate, - ) - } else { - state.selectedFeeItem.fee.amount.value.format { - crypto( - symbol = state.selectedFeeItem.fee.amount.currencySymbol, - decimals = state.selectedFeeItem.fee.amount.decimals, - ).fee(canBeLower = state.isFeeApproximate) - } - }, - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.tertiary, - textAlign = TextAlign.End, - modifier = Modifier - .weight(1f) - .padding(start = TangemTheme.dimens.spacing4), - ) - Icon( - modifier = Modifier.size(width = 18.dp, height = 24.dp), - painter = painterResource(id = R.drawable.ic_select_18_24), - contentDescription = null, - tint = TangemTheme.colors.icon.informative, - ) -} - -@Preview(showBackground = true, device = Devices.PIXEL_7_PRO) -@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun FeeSelectorBlockContent_Preview() { - TangemThemePreview { - val feeItem = FeeItem.Market( - Fee.Common(amount = Amount(value = BigDecimal("0.0002876"), blockchain = Blockchain.Ethereum)), - ) - FeeSelectorBlockContent( - modifier = Modifier.fillMaxWidth(), - state = FeeSelectorUM.Content( - feeItems = persistentListOf(feeItem), - selectedFeeItem = feeItem, - isFeeApproximate = false, - feeFiatRateUM = FeeFiatRateUM( - rate = BigDecimal("2500"), - appCurrency = AppCurrency.Default, - ), - displayNonceInput = false, - nonce = null, - onNonceChange = {}, - fees = TransactionFee.Single(feeItem.fee), - ), - ) - } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/DefaultFeeSelectorComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/DefaultFeeSelectorComponent.kt index 0166fadf8f..aae3d79280 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/DefaultFeeSelectorComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/DefaultFeeSelectorComponent.kt @@ -16,18 +16,24 @@ import dagger.assisted.AssistedInject internal class DefaultFeeSelectorComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @Assisted private val params: FeeSelectorParams.FeeSelectorDetailsParams, + @Assisted private val onDismiss: () -> Unit, ) : FeeSelectorComponent, AppComponentContext by appComponentContext { private val model: FeeSelectorModel = getOrCreateModel(params = params) override fun dismiss() { - model.dismiss() + onDismiss() } @Composable override fun BottomSheet() { val state by model.uiState.collectAsStateWithLifecycle() - FeeSelectorModalBottomSheet(onDismiss = ::dismiss, state = state, feeSelectorIntents = model) + FeeSelectorModalBottomSheet( + onDismiss = ::dismiss, + state = state, + feeSelectorIntents = model, + feeDisplaySource = params.feeDisplaySource, + ) } @AssistedFactory @@ -35,6 +41,7 @@ internal class DefaultFeeSelectorComponent @AssistedInject constructor( override fun create( context: AppComponentContext, params: FeeSelectorParams.FeeSelectorDetailsParams, + onDismiss: () -> Unit, ): DefaultFeeSelectorComponent } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorModel.kt index d15ee2d65b..183224d789 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorModel.kt @@ -2,14 +2,16 @@ package com.tangem.features.send.v2.feeselector.model import androidx.compose.runtime.Stable import arrow.core.getOrElse +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.dismiss import com.tangem.blockchain.common.AmountType 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.decompose.navigation.Router import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase +import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback import com.tangem.features.send.v2.api.entity.FeeItem import com.tangem.features.send.v2.api.entity.FeeSelectorUM import com.tangem.features.send.v2.api.params.FeeSelectorParams @@ -30,13 +32,14 @@ internal class FeeSelectorModel @Inject constructor( paramsContainer: ParamsContainer, private val isFeeApproximateUseCase: IsFeeApproximateUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, - private val router: Router, override val dispatchers: CoroutineDispatcherProvider, -) : Model(), FeeSelectorIntents { +) : Model(), FeeSelectorIntents, FeeSelectorModelCallback { private val params = paramsContainer.require() private var appCurrency: AppCurrency = AppCurrency.Default + val feeSelectorBottomSheet = SlotNavigation() + val uiState: StateFlow field = MutableStateFlow(params.state) @@ -49,10 +52,6 @@ internal class FeeSelectorModel @Inject constructor( uiState.value = feeSelectorUM } - fun dismiss() { - router.pop() - } - private fun initAppCurrency() { modelScope.launch { appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default } @@ -102,7 +101,11 @@ internal class FeeSelectorModel @Inject constructor( } override fun onDoneClick() { - params.callback.onFeeResult(uiState.value) - dismiss() + (params as? FeeSelectorParams.FeeSelectorDetailsParams)?.callback?.onFeeResult(uiState.value) + } + + override fun onFeeResult(feeSelectorUM: FeeSelectorUM) { + uiState.value = feeSelectorUM + feeSelectorBottomSheet.dismiss() } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorBlockContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorBlockContent.kt new file mode 100644 index 0000000000..b858c042ee --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorBlockContent.kt @@ -0,0 +1,153 @@ +package com.tangem.features.send.v2.feeselector.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.common.ui.amountScreen.utils.getFiatString +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.components.TextShimmer +import com.tangem.core.ui.components.atoms.text.EllipsisText +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.EMPTY_BALANCE_SIGN +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fee +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.features.send.v2.api.entity.FeeFiatRateUM +import com.tangem.features.send.v2.api.entity.FeeItem +import com.tangem.features.send.v2.api.entity.FeeSelectorUM +import com.tangem.features.send.v2.impl.R +import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal + +@Composable +internal fun FeeSelectorBlockContent(state: FeeSelectorUM, modifier: Modifier = Modifier) { + Row( + modifier = modifier + .background(TangemTheme.colors.background.action) + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.ic_fee_new_24), + contentDescription = null, + tint = TangemTheme.colors.icon.accent, + ) + Text( + modifier = Modifier.padding(start = TangemTheme.dimens.spacing4), + text = stringResourceSafe(R.string.common_network_fee_title), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + ) + Icon( + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing6) + .size(TangemTheme.dimens.size16), + painter = painterResource(id = R.drawable.ic_token_info_24), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + ) + SpacerWMax() + when (state) { + is FeeSelectorUM.Content -> FeeContent(state) + is FeeSelectorUM.Loading -> FeeLoading() + is FeeSelectorUM.Error -> FeeError() + } + } +} + +@Composable +private fun RowScope.FeeError() { + Text( + text = EMPTY_BALANCE_SIGN, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.body2, + ) +} + +@Composable +private fun RowScope.FeeLoading() { + TextShimmer( + radius = TangemTheme.dimens.radius3, + style = TangemTheme.typography.body1, + modifier = Modifier.width(width = TangemTheme.dimens.size90), + ) +} + +@Composable +private fun RowScope.FeeContent(state: FeeSelectorUM.Content) { + val fiatRate = state.feeFiatRateUM + EllipsisText( + text = if (fiatRate != null) { + getFiatString( + value = state.selectedFeeItem.fee.amount.value, + rate = fiatRate.rate, + appCurrency = fiatRate.appCurrency, + approximate = state.isFeeApproximate, + ) + } else { + state.selectedFeeItem.fee.amount.value.format { + crypto( + symbol = state.selectedFeeItem.fee.amount.currencySymbol, + decimals = state.selectedFeeItem.fee.amount.decimals, + ).fee(canBeLower = state.isFeeApproximate) + } + }, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.End, + modifier = Modifier + .weight(1f) + .padding(start = TangemTheme.dimens.spacing4), + ) + Icon( + modifier = Modifier.size(width = 18.dp, height = 24.dp), + painter = painterResource(id = R.drawable.ic_select_18_24), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + ) +} + +@Preview(showBackground = true, device = Devices.PIXEL_7_PRO) +@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun FeeSelectorBlockContent_Preview() { + TangemThemePreview { + val feeItem = FeeItem.Market( + Fee.Common(amount = Amount(value = BigDecimal("0.0002876"), blockchain = Blockchain.Ethereum)), + ) + FeeSelectorBlockContent( + modifier = Modifier.fillMaxWidth(), + state = FeeSelectorUM.Content( + feeItems = persistentListOf(feeItem), + selectedFeeItem = feeItem, + isFeeApproximate = false, + feeFiatRateUM = FeeFiatRateUM( + rate = BigDecimal("2500"), + appCurrency = AppCurrency.Default, + ), + displayNonceInput = false, + nonce = null, + onNonceChange = {}, + fees = TransactionFee.Single(feeItem.fee), + ), + ) + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt index fc27111f09..ccde03a163 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt @@ -4,10 +4,8 @@ import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.animation.* import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.HorizontalDivider @@ -17,7 +15,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue 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.res.painterResource import androidx.compose.ui.text.input.KeyboardType @@ -52,6 +49,7 @@ import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM import com.tangem.features.send.v2.api.entity.FeeFiatRateUM import com.tangem.features.send.v2.api.entity.FeeItem import com.tangem.features.send.v2.api.entity.FeeSelectorUM +import com.tangem.features.send.v2.api.params.FeeSelectorParams import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents import com.tangem.features.send.v2.feeselector.model.StubFeeSelectorIntents import com.tangem.features.send.v2.impl.R @@ -65,6 +63,7 @@ import java.math.BigInteger internal fun FeeSelectorModalBottomSheet( state: FeeSelectorUM, feeSelectorIntents: FeeSelectorIntents, + feeDisplaySource: FeeSelectorParams.FeeDisplaySource, onDismiss: () -> Unit, ) { if (state !is FeeSelectorUM.Content) return @@ -77,17 +76,13 @@ internal fun FeeSelectorModalBottomSheet( ), containerColor = TangemTheme.colors.background.primary, title = { - TangemModalBottomSheetTitle( - title = resourceReference(R.string.common_network_fee_title), - startIconRes = R.drawable.ic_back_24, - onStartClick = onDismiss, - ) + FeeTitle(feeDisplaySource = feeDisplaySource, onDismiss = onDismiss) }, content = { FeeSelectorItems( state = state, feeSelectorIntents = feeSelectorIntents, - modifier = Modifier.padding(vertical = 4.dp, horizontal = 16.dp), + modifier = Modifier.padding(vertical = 4.dp, horizontal = 13.dp), ) }, footer = { @@ -102,6 +97,26 @@ internal fun FeeSelectorModalBottomSheet( ) } +@Composable +private fun FeeTitle(feeDisplaySource: FeeSelectorParams.FeeDisplaySource, onDismiss: () -> Unit) { + when (feeDisplaySource) { + FeeSelectorParams.FeeDisplaySource.Screen -> { + TangemModalBottomSheetTitle( + title = resourceReference(R.string.common_network_fee_title), + endIconRes = R.drawable.ic_close_24, + onEndClick = onDismiss, + ) + } + FeeSelectorParams.FeeDisplaySource.BottomSheet -> { + TangemModalBottomSheetTitle( + title = resourceReference(R.string.common_network_fee_title), + startIconRes = R.drawable.ic_back_24, + onStartClick = onDismiss, + ) + } + } +} + @Suppress("LongMethod", "CyclomaticComplexMethod") @Composable private fun FeeSelectorItems( @@ -129,21 +144,7 @@ private fun FeeSelectorItems( val itemModifier = Modifier .fillMaxWidth() .background(TangemTheme.colors.background.primary) - .then( - if (isSelected) { - Modifier - .border( - width = 2.5.dp, - color = iconTint.copy(alpha = 0.2F), - shape = RoundedCornerShape(16.dp), - ) - .padding(2.5.dp) - .border(width = 1.dp, color = iconTint, shape = RoundedCornerShape(14.dp)) - .clip(RoundedCornerShape(14.dp)) - } else { - Modifier - }, - ) + .selectedBorder(isSelected = isSelected) .clickableSingle(onClick = { feeSelectorIntents.onFeeItemSelected(item) }) when (item) { is FeeItem.Suggested -> RegularFeeItemContent( @@ -477,7 +478,12 @@ private fun FeeSelectorBS_Preview( state: FeeSelectorUM.Content, ) { TangemThemePreview { - FeeSelectorModalBottomSheet(onDismiss = {}, state = state, feeSelectorIntents = StubFeeSelectorIntents()) + FeeSelectorModalBottomSheet( + onDismiss = {}, + state = state, + feeSelectorIntents = StubFeeSelectorIntents(), + feeDisplaySource = FeeSelectorParams.FeeDisplaySource.BottomSheet, + ) } } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcNavigationUtils.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcNavigationUtils.kt index 7bbe97cb99..7089bc96f9 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcNavigationUtils.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcNavigationUtils.kt @@ -41,12 +41,14 @@ internal fun getWcCommonScreen( val state = model.uiState.value ?: error("in this step state should be not null") feeSelectorComponentFactory.create( context = appComponentContext, + onDismiss = model::popBack, params = FeeSelectorParams.FeeSelectorDetailsParams( state = state.feeSelectorUM, onLoadFee = model::loadFee, cryptoCurrencyStatus = model.cryptoCurrencyStatus, callback = model, suggestedFeeState = model.suggestedFeeState, + feeDisplaySource = FeeSelectorParams.FeeDisplaySource.BottomSheet, ), ) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendTransactionComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendTransactionComponent.kt index b7e2f9a519..26b445e05c 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendTransactionComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendTransactionComponent.kt @@ -26,9 +26,10 @@ internal class WcSendTransactionComponent( state = state.feeSelectorUM, onLoadFee = model::loadFee, cryptoCurrencyStatus = model.cryptoCurrencyStatus, - callback = model, suggestedFeeState = model.suggestedFeeState, + feeDisplaySource = FeeSelectorParams.FeeDisplaySource.BottomSheet, ), + onResult = model::updateFee, ) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcCommonTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcCommonTransactionModel.kt index bf422a8b13..c8afce4c4a 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcCommonTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcCommonTransactionModel.kt @@ -7,4 +7,6 @@ internal interface WcCommonTransactionModel { val uiState: StateFlow fun dismiss() + + fun popBack() } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt index 2ca9466415..911d108202 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt @@ -67,6 +67,10 @@ internal class WcAddNetworkModel @Inject constructor( _uiState.value?.transaction?.onDismiss?.invoke() ?: router.pop() } + override fun popBack() { + router.pop() + } + fun showTransactionRequest() { stackNavigation.pushNew(WcTransactionRoutes.TransactionRequestInfo) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt index 7e2951dbb6..db845051ad 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt @@ -118,7 +118,20 @@ internal class WcSendTransactionModel @Inject constructor( } } + /** + * Handles callback with updated [feeSelectorUM] from click FeeSelectorComponent. + * We need to trigger navigation to dismiss fee selector component + */ override fun onFeeResult(feeSelectorUM: FeeSelectorUM) { + updateFee(feeSelectorUM) + popBack() + } + + /** + * Handles fee updates. + * Also handles fee results from FeeSelectorBlockComponent + */ + fun updateFee(feeSelectorUM: FeeSelectorUM) { _uiState.update { it?.copy(feeSelectorUM = feeSelectorUM) } val fee = (feeSelectorUM as? FeeSelectorUM.Content)?.selectedFeeItem?.fee ?: return (useCase as? WcMutableFee)?.updateFee(fee) @@ -186,6 +199,10 @@ internal class WcSendTransactionModel @Inject constructor( _uiState.value?.transaction?.onDismiss?.invoke() ?: router.pop() } + override fun popBack() { + stackNavigation.pop() + } + fun showTransactionRequest() { stackNavigation.pushNew(WcTransactionRoutes.TransactionRequestInfo) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt index f61e0b2545..34252a2a2e 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt @@ -2,6 +2,7 @@ package com.tangem.features.walletconnect.transaction.model import androidx.compose.runtime.Stable import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.router.stack.pushNew import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model @@ -76,6 +77,10 @@ internal class WcSignTransactionModel @Inject constructor( _uiState.value?.transaction?.onDismiss?.invoke() ?: router.pop() } + override fun popBack() { + stackNavigation.pop() + } + fun showTransactionRequest() { stackNavigation.pushNew(WcTransactionRoutes.TransactionRequestInfo) }