diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9a66725eb7..e3f7a4fcde 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -50,6 +50,7 @@ dependencies {
implementation(projects.domain.appTheme.models)
implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models)
+ implementation(projects.domain.transaction)
implementation(projects.common)
implementation(projects.core.analytics)
diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt
new file mode 100644
index 0000000000..5b68512d77
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt
@@ -0,0 +1,24 @@
+package com.tangem.tap.di.domain
+
+import com.tangem.domain.transaction.usecase.GetFeeUseCase
+import com.tangem.domain.walletmanager.WalletManagersFacade
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ViewModelComponent
+import dagger.hilt.android.scopes.ViewModelScoped
+
+@Module
+@InstallIn(ViewModelComponent::class)
+internal object TransactionDomainModule {
+
+ @Provides
+ @ViewModelScoped
+ fun provideGetUseCase(
+ walletManagersFacade: WalletManagersFacade,
+ dispatchers: CoroutineDispatcherProvider,
+ ): GetFeeUseCase {
+ return GetFeeUseCase(walletManagersFacade, dispatchers)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt
index 78084743b7..74815a675f 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt
@@ -18,7 +18,7 @@ import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item
-import com.tangem.tap.features.details.ui.common.TangemSwitch
+import com.tangem.core.ui.components.TangemSwitch
@Composable
internal fun SettingsSwitchItem(item: Item.Switch, modifier: Modifier = Modifier) {
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index b26afb1dea..6bd6a2445a 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -421,7 +421,7 @@
Priority
Check your network connection
Network fee info unreachable
- From **%s**
+ From
Gas limit
Gas Limit is auto-calculated; raise it during network congestion
Gas price
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt b/core/ui/src/main/java/com/tangem/core/ui/components/TangemSwitch.kt
similarity index 98%
rename from app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt
rename to core/ui/src/main/java/com/tangem/core/ui/components/TangemSwitch.kt
index 9839280049..c4d41ef91b 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/TangemSwitch.kt
@@ -1,4 +1,4 @@
-package com.tangem.tap.features.details.ui.common
+package com.tangem.core.ui.components
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.*
diff --git a/core/ui/src/main/res/drawable/ic_bird_24.xml b/core/ui/src/main/res/drawable/ic_bird_24.xml
new file mode 100644
index 0000000000..234ea4f693
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_bird_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_edit_24.xml b/core/ui/src/main/res/drawable/ic_edit_24.xml
new file mode 100644
index 0000000000..cc2504eb91
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_edit_24.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_hare_24.xml b/core/ui/src/main/res/drawable/ic_hare_24.xml
new file mode 100644
index 0000000000..e2714dc9d0
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_hare_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_tortoise_24.xml b/core/ui/src/main/res/drawable/ic_tortoise_24.xml
new file mode 100644
index 0000000000..8dee215fd1
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_tortoise_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
index bc1bf00b14..a581d0d2fa 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
@@ -10,6 +10,7 @@ import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.address.AddressType
+import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.common.txhistory.TransactionHistoryRequest
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
@@ -411,6 +412,24 @@ class DefaultWalletManagersFacade(
}
}
+ override suspend fun getFee(
+ amount: Amount,
+ destination: String,
+ userWalletId: UserWalletId,
+ network: Network,
+ ): Result? {
+ val blockchain = Blockchain.fromId(network.id.value)
+ val walletManager = getOrCreateWalletManager(
+ userWalletId = userWalletId,
+ blockchain = blockchain,
+ derivationPath = network.derivationPath.value,
+ )
+ return (walletManager as? TransactionSender)?.getFee(
+ amount = amount,
+ destination = destination,
+ )
+ }
+
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set) {
if (tokens.isEmpty()) return
diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt
index 31345d6f99..8e830e0bdd 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt
@@ -2,10 +2,13 @@ package com.tangem.domain.walletmanager
import arrow.core.Either
import com.tangem.blockchain.blockchains.solana.RentProvider
+import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.address.AddressType
+import com.tangem.blockchain.common.transaction.TransactionFee
+import com.tangem.blockchain.extensions.Result
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
@@ -143,4 +146,19 @@ interface WalletManagersFacade {
network: Network,
signedHashes: Int,
): Either
+
+ /**
+ * Returns fee for transaction
+ *
+ * @param amount of transaction
+ * @param destination address
+ * @param userWalletId selected wallet id
+ * @param network network of currency
+ */
+ suspend fun getFee(
+ amount: Amount,
+ destination: String,
+ userWalletId: UserWalletId,
+ network: Network,
+ ): Result?
}
\ No newline at end of file
diff --git a/domain/transaction/.gitignore b/domain/transaction/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/domain/transaction/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/domain/transaction/build.gradle.kts b/domain/transaction/build.gradle.kts
new file mode 100644
index 0000000000..8da795757a
--- /dev/null
+++ b/domain/transaction/build.gradle.kts
@@ -0,0 +1,23 @@
+plugins {
+ alias(deps.plugins.android.library)
+ alias(deps.plugins.kotlin.android)
+ id("configuration")
+}
+
+android {
+ namespace = "com.tangem.domain.transaction"
+}
+
+dependencies {
+ implementation(deps.kotlin.coroutines)
+ implementation(deps.arrow.core)
+
+ implementation(projects.core.utils)
+
+ implementation(deps.tangem.blockchain)
+
+ implementation(projects.domain.legacy)
+ implementation(projects.domain.wallets.models)
+ implementation(projects.domain.tokens)
+ implementation(projects.domain.tokens.models)
+}
\ No newline at end of file
diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/GetFeeError.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/GetFeeError.kt
new file mode 100644
index 0000000000..f08011b2d4
--- /dev/null
+++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/GetFeeError.kt
@@ -0,0 +1,5 @@
+package com.tangem.domain.transaction.error
+
+sealed class GetFeeError {
+ object DataError : GetFeeError()
+}
\ No newline at end of file
diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt
new file mode 100644
index 0000000000..dee4c3cdce
--- /dev/null
+++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt
@@ -0,0 +1,65 @@
+package com.tangem.domain.transaction.usecase
+
+import arrow.core.Either
+import arrow.core.left
+import arrow.core.right
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.Token
+import com.tangem.blockchain.common.transaction.TransactionFee
+import com.tangem.blockchain.extensions.Result
+import com.tangem.domain.tokens.model.CryptoCurrency
+import com.tangem.domain.transaction.error.GetFeeError
+import com.tangem.domain.walletmanager.WalletManagersFacade
+import com.tangem.domain.wallets.models.UserWalletId
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import java.math.BigDecimal
+
+/**
+ * Use case to get transaction fee
+ */
+class GetFeeUseCase(
+ private val walletManagersFacade: WalletManagersFacade,
+ private val dispatcher: CoroutineDispatcherProvider,
+) {
+ suspend operator fun invoke(
+ amount: BigDecimal,
+ destination: String,
+ userWalletId: UserWalletId,
+ cryptoCurrency: CryptoCurrency,
+ ): Flow> {
+ return flow {
+ val result = walletManagersFacade.getFee(
+ amount = convertCryptoCurrencyToAmount(cryptoCurrency, amount),
+ destination = destination,
+ userWalletId = userWalletId,
+ network = cryptoCurrency.network,
+ )
+
+ val maybeFee = when (result) {
+ is Result.Success -> result.data.right()
+ else -> GetFeeError.DataError.left()
+ }
+ emit(maybeFee)
+ }.flowOn(dispatcher.io)
+ }
+
+ private fun convertCryptoCurrencyToAmount(cryptoCurrency: CryptoCurrency, amount: BigDecimal) = Amount(
+ currencySymbol = cryptoCurrency.symbol,
+ value = amount,
+ decimals = cryptoCurrency.decimals,
+ type = when (cryptoCurrency) {
+ is CryptoCurrency.Coin -> AmountType.Coin
+ is CryptoCurrency.Token -> AmountType.Token(
+ token = Token(
+ symbol = cryptoCurrency.symbol,
+ contractAddress = cryptoCurrency.contractAddress,
+ decimals = cryptoCurrency.decimals,
+ ),
+ )
+ },
+ )
+}
\ No newline at end of file
diff --git a/features/send/impl/build.gradle.kts b/features/send/impl/build.gradle.kts
index 65f362206d..bb59a8e295 100644
--- a/features/send/impl/build.gradle.kts
+++ b/features/send/impl/build.gradle.kts
@@ -57,6 +57,7 @@ dependencies {
implementation(projects.domain.appCurrency.models)
implementation(projects.domain.txhistory)
implementation(projects.domain.txhistory.models)
+ implementation(projects.domain.transaction)
/** Feature modules */
implementation(projects.features.send.api)
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt
index 87ac690720..3bb47678a9 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt
@@ -2,6 +2,7 @@ package com.tangem.features.send.impl.presentation.state
import androidx.paging.PagingData
import com.tangem.blockchain.common.address.Address
+import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.Provider
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.TextReference
@@ -12,6 +13,10 @@ import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter
+import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
+import com.tangem.features.send.impl.presentation.state.fee.FeeType
+import com.tangem.features.send.impl.presentation.state.fee.SendFeeCustomFieldConverter
+import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientListConverter
@@ -35,8 +40,13 @@ internal class SendStateFactory(
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
private val amountFieldConverter by lazy { SendAmountFieldConverter(clickIntents) }
-
private val amountFieldChangeConverter by lazy { SendAmountFieldChangeConverter(currentStateProvider) }
+ private val customFeeFieldConverter by lazy {
+ SendFeeCustomFieldConverter(
+ clickIntents = clickIntents,
+ appCurrencyProvider = appCurrencyProvider,
+ )
+ }
private val amountStateConverter by lazy {
SendAmountStateConverter(
@@ -47,13 +57,17 @@ internal class SendStateFactory(
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
)
}
-
private val recipientStateConverter by lazy {
SendRecipientStateConverter(
clickIntents = clickIntents,
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
)
}
+ private val feeStateConverter by lazy {
+ SendFeeStateConverter(
+ cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
+ )
+ }
private val recipientListStateConverter by lazy {
SendRecipientListConverter(
@@ -71,7 +85,7 @@ internal class SendStateFactory(
fun getReadyState(): SendUiState = currentStateProvider().copy(
amountState = amountStateConverter.convert(Unit),
recipientState = recipientStateConverter.convert(Unit),
- feeState = SendStates.FeeState(),
+ feeState = feeStateConverter.convert(Unit),
)
//endregion
@@ -164,5 +178,28 @@ internal class SendStateFactory(
),
)
}
+
+ fun onFeeOnLoadingState() {
+ currentStateProvider().feeState?.feeSelectorState?.update {
+ FeeSelectorState.Loading
+ }
+ }
+
+ fun onFeeOnLoadedState(fees: TransactionFee) {
+ currentStateProvider().feeState?.feeSelectorState?.update {
+ FeeSelectorState.Content(
+ fees = fees,
+ customValues = customFeeFieldConverter.convert(fees.normal),
+ )
+ }
+ }
+ //endregion
+
+ //region fee
+ fun onFeeSelectedState(feeType: FeeType) {
+ currentStateProvider().feeState?.feeSelectorState?.update {
+ (it as? FeeSelectorState.Content)?.copy(selectedFee = feeType) ?: it
+ }
+ }
//endregion
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt
index 6baeb27cfb..207e63064d 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt
@@ -8,6 +8,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent
import com.tangem.features.send.impl.presentation.state.amount.SendAmountSegmentedButtonsConfig
+import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import kotlinx.collections.immutable.PersistentList
@@ -55,10 +56,13 @@ internal sealed class SendStates {
val isPrimaryButtonEnabled: Boolean,
) : SendStates()
- // todo [REDACTED_JIRA]
/** Fee and speed state */
data class FeeState(
override val type: SendUiStateType = SendUiStateType.Fee,
+ val cryptoCurrencyStatus: CryptoCurrencyStatus,
+ val feeSelectorState: MutableStateFlow = MutableStateFlow(FeeSelectorState.Empty),
+ val isSubtract: MutableStateFlow = MutableStateFlow(false),
+ val receivedAmount: MutableStateFlow = MutableStateFlow(""),
) : SendStates()
// todo [REDACTED_JIRA]
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt
index e7526fdf04..e50ae05ae8 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt
@@ -16,15 +16,11 @@ internal class StateRouter(
fun onNextClick() {
when (currentState.value) {
- SendUiStateType.Amount -> {
- currentState.update { SendUiStateType.Recipient }
- }
- SendUiStateType.Recipient -> {
- currentState.update { SendUiStateType.Fee }
- }
- else -> {
- // todo implement
- }
+ SendUiStateType.Amount -> currentState.update { SendUiStateType.Recipient }
+ SendUiStateType.Recipient -> currentState.update { SendUiStateType.Fee }
+ SendUiStateType.Fee -> currentState.update { SendUiStateType.Send }
+ SendUiStateType.Send -> currentState.update { SendUiStateType.Done }
+ SendUiStateType.Done -> onBackClick()
}
}
@@ -33,7 +29,8 @@ internal class StateRouter(
SendUiStateType.Amount -> onBackClick()
SendUiStateType.Recipient -> currentState.update { SendUiStateType.Amount }
SendUiStateType.Fee -> currentState.update { SendUiStateType.Recipient }
- else -> onBackClick()
+ SendUiStateType.Send -> currentState.update { SendUiStateType.Fee }
+ SendUiStateType.Done -> onBackClick()
}
}
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt
new file mode 100644
index 0000000000..f920348ac8
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt
@@ -0,0 +1,27 @@
+package com.tangem.features.send.impl.presentation.state.fee
+
+import androidx.compose.runtime.Immutable
+import com.tangem.blockchain.common.transaction.TransactionFee
+import com.tangem.features.send.impl.presentation.state.fields.SendTextField
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@Immutable
+internal sealed class FeeSelectorState {
+
+ object Loading : FeeSelectorState()
+
+ object Empty : FeeSelectorState()
+
+ data class Content(
+ val fees: TransactionFee,
+ val selectedFee: FeeType = FeeType.MARKET,
+ val customValues: MutableStateFlow> = MutableStateFlow(emptyList()),
+ ) : FeeSelectorState()
+}
+
+enum class FeeType {
+ SLOW,
+ MARKET,
+ FAST,
+ CUSTOM,
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt
new file mode 100644
index 0000000000..39851a417a
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt
@@ -0,0 +1,61 @@
+package com.tangem.features.send.impl.presentation.state.fee
+
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import com.tangem.blockchain.common.transaction.Fee
+import com.tangem.common.Provider
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.utils.BigDecimalFormatter
+import com.tangem.domain.appcurrency.model.AppCurrency
+import com.tangem.features.send.impl.presentation.state.fields.SendTextField
+import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
+import com.tangem.utils.converter.Converter
+import kotlinx.coroutines.flow.MutableStateFlow
+
+internal class SendFeeCustomFieldConverter(
+ private val clickIntents: SendClickIntents,
+ private val appCurrencyProvider: Provider,
+) : Converter>> {
+
+ override fun convert(value: Fee): MutableStateFlow> {
+ val ethereumFee = value as? Fee.Ethereum ?: return MutableStateFlow(emptyList())
+ val appCurrency = appCurrencyProvider()
+
+ val maxFeeFiat = BigDecimalFormatter.formatFiatAmount(
+ fiatAmount = ethereumFee.amount.value,
+ fiatCurrencyCode = appCurrency.code,
+ fiatCurrencySymbol = appCurrency.symbol,
+ )
+
+ return MutableStateFlow(
+ listOf(
+ SendTextField.CustomFee(
+ value = ethereumFee.amount.value.toString(),
+ onValueChange = { clickIntents.onCustomFeeValueChange(0, it) },
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next,
+ keyboardType = KeyboardType.Number,
+ ),
+ label = TextReference.Str(maxFeeFiat),
+ ),
+ SendTextField.CustomFee(
+ value = ethereumFee.gasPrice.toString(),
+ onValueChange = { clickIntents.onCustomFeeValueChange(1, it) },
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next,
+ keyboardType = KeyboardType.Number,
+ ),
+ ),
+ SendTextField.CustomFee(
+ value = ethereumFee.gasLimit.toString(),
+ onValueChange = { clickIntents.onCustomFeeValueChange(2, it) },
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Done,
+ keyboardType = KeyboardType.Number,
+ ),
+ ),
+ ),
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt
new file mode 100644
index 0000000000..13a2ebe5c2
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt
@@ -0,0 +1,17 @@
+package com.tangem.features.send.impl.presentation.state.fee
+
+import com.tangem.common.Provider
+import com.tangem.domain.tokens.model.CryptoCurrencyStatus
+import com.tangem.features.send.impl.presentation.state.SendStates
+import com.tangem.utils.converter.Converter
+
+internal class SendFeeStateConverter(
+ private val cryptoCurrencyStatusProvider: Provider,
+) : Converter {
+
+ override fun convert(value: Unit): SendStates.FeeState {
+ return SendStates.FeeState(
+ cryptoCurrencyStatus = cryptoCurrencyStatusProvider(),
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt
index 93a0f62d57..c8f289df54 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt
@@ -20,6 +20,7 @@ import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.SendUiStateType
import com.tangem.features.send.impl.presentation.ui.amount.SendAmountContent
+import com.tangem.features.send.impl.presentation.ui.fee.SendSpeedAndFeeContent
import com.tangem.features.send.impl.presentation.ui.recipient.SendRecipientContent
@Composable
@@ -89,6 +90,10 @@ private fun SendScreenContent(
uiState.clickIntents,
recipientList,
)
+ SendUiStateType.Fee -> SendSpeedAndFeeContent(
+ uiState.feeState,
+ uiState.clickIntents,
+ )
else -> { /* [REDACTED_TODO_COMMENT]*/ }
}
}
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/FooterContainer.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/FooterContainer.kt
index ce0fbcee3b..26b442e62a 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/FooterContainer.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/FooterContainer.kt
@@ -1,5 +1,6 @@
package com.tangem.features.send.impl.presentation.ui.common
+import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
@@ -25,9 +26,9 @@ internal fun FooterContainer(
) {
Column(modifier = modifier) {
content()
- footer?.let {
+ AnimatedVisibility(visible = footer != null) {
Text(
- text = it,
+ text = footer.orEmpty(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt
new file mode 100644
index 0000000000..a96d8c5f1b
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt
@@ -0,0 +1,63 @@
+package com.tangem.features.send.impl.presentation.ui.fee
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.tangem.core.ui.components.fields.AmountVisualTransformation
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.features.send.impl.R
+import com.tangem.features.send.impl.presentation.state.fee.FeeType
+import com.tangem.features.send.impl.presentation.state.fields.SendTextField
+import com.tangem.features.send.impl.presentation.ui.recipient.TextFieldWithInfo
+
+private const val ETHEREUM_UNIT = "GWEI"
+
+@Composable
+internal fun SendCustomFeeEthereum(
+ customValues: State>,
+ selectedFee: FeeType,
+ symbol: String,
+ modifier: Modifier = Modifier,
+) {
+ val fee = customValues.value[0]
+ val gasPrice = customValues.value[1]
+ val gasLimit = customValues.value[2]
+
+ if (selectedFee == FeeType.CUSTOM && customValues.value.isNotEmpty()) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
+ modifier = modifier,
+ ) {
+ TextFieldWithInfo(
+ value = fee.value,
+ label = stringResource(R.string.send_max_fee),
+ footer = stringResource(R.string.send_max_fee_footer),
+ info = fee.label,
+ visualTransformation = AmountVisualTransformation(symbol),
+ keyboardOptions = fee.keyboardOptions,
+ onValueChange = fee.onValueChange,
+ isSingleLine = true,
+ )
+ TextFieldWithInfo(
+ value = gasPrice.value,
+ label = stringResource(R.string.send_gas_price),
+ footer = stringResource(R.string.send_gas_price_footer),
+ onValueChange = gasPrice.onValueChange,
+ visualTransformation = AmountVisualTransformation(ETHEREUM_UNIT),
+ keyboardOptions = fee.keyboardOptions,
+ isSingleLine = true,
+ )
+ TextFieldWithInfo(
+ value = gasLimit.value,
+ label = stringResource(R.string.send_gas_limit),
+ footer = stringResource(R.string.send_gas_limit_footer),
+ onValueChange = gasLimit.onValueChange,
+ keyboardOptions = fee.keyboardOptions,
+ isSingleLine = true,
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt
new file mode 100644
index 0000000000..595d04491a
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt
@@ -0,0 +1,86 @@
+package com.tangem.features.send.impl.presentation.ui.fee
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.features.send.impl.presentation.state.SendStates
+import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
+import com.tangem.features.send.impl.presentation.state.fee.FeeType
+import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
+
+private const val FEE_SELECTOR_KEY = "FEE_SELECTOR_KEY"
+private const val FEE_CUSTOM_KEY = "FEE_CUSTOM_KEY"
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+internal fun SendSpeedAndFeeContent(state: SendStates.FeeState?, clickIntents: SendClickIntents) {
+ if (state == null) return
+ val feeSendState = state.feeSelectorState.collectAsStateWithLifecycle()
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(TangemTheme.colors.background.tertiary)
+ .padding(
+ horizontal = TangemTheme.dimens.spacing16,
+ ),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
+ ) {
+ item(
+ key = FEE_SELECTOR_KEY,
+ ) {
+ SendSpeedSelector(
+ state = feeSendState,
+ clickIntents = clickIntents,
+ )
+ }
+ if (feeSendState.value is FeeSelectorState.Content) {
+ item(
+ key = FEE_CUSTOM_KEY,
+ ) {
+ AnimatedVisibility(
+ visible = feeSendState.value is FeeSelectorState.Content,
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(TangemTheme.colors.background.tertiary),
+ ) {
+ val fee = feeSendState.value as FeeSelectorState.Content
+ val customValues = fee.customValues.collectAsStateWithLifecycle()
+ SendCustomFeeEthereum(
+ customValues = customValues,
+ selectedFee = fee.selectedFee,
+ symbol = state.cryptoCurrencyStatus.currency.symbol,
+ modifier = Modifier
+ .animateItemPlacement(),
+ )
+ }
+ }
+ }
+ item {
+ val topPadding = (feeSendState.value as? FeeSelectorState.Content)?.let { state ->
+ if (state.selectedFee != FeeType.CUSTOM) {
+ TangemTheme.dimens.spacing8
+ } else {
+ TangemTheme.dimens.spacing0
+ }
+ } ?: TangemTheme.dimens.spacing0
+
+ SendSpeedSubtract(
+ receivingAmount = state.receivedAmount,
+ isSubtract = state.isSubtract,
+ onSelectClick = clickIntents::onSubtractSelect,
+ modifier = Modifier
+ .animateItemPlacement()
+ .padding(
+ top = topPadding,
+ bottom = TangemTheme.dimens.spacing12,
+ ),
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt
new file mode 100644
index 0000000000..de69e011d6
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt
@@ -0,0 +1,271 @@
+package com.tangem.features.send.impl.presentation.ui.fee
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+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.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import com.tangem.blockchain.common.transaction.Fee
+import com.tangem.blockchain.common.transaction.TransactionFee
+import com.tangem.core.ui.components.RectangleShimmer
+import com.tangem.core.ui.components.SpacerWMax
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.features.send.impl.R
+import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
+import com.tangem.features.send.impl.presentation.state.fee.FeeType
+import com.tangem.features.send.impl.presentation.ui.common.FooterContainer
+import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
+
+@Suppress("LongMethod")
+@Composable
+internal fun SendSpeedSelector(
+ state: State,
+ clickIntents: SendClickIntents,
+ modifier: Modifier = Modifier,
+) {
+ FooterContainer(
+ footer = stringResource(R.string.common_fee_selector_footer),
+ modifier = modifier,
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(TangemTheme.shapes.roundedCornersXMedium)
+ .background(TangemTheme.colors.background.action),
+ ) {
+ when (val selector = state.value) {
+ FeeSelectorState.Loading -> {
+ SendSpeedSelectorItemLoading()
+ SendSpeedSelectorItemLoading()
+ SendSpeedSelectorItemLoading()
+ }
+ is FeeSelectorState.Content -> {
+ when (selector.fees) {
+ is TransactionFee.Choosable -> {
+ SendSpeedSelectorItem(
+ titleRes = R.string.common_fee_selector_option_slow,
+ iconRes = R.drawable.ic_tortoise_24,
+ amount = TextReference.Str(selector.fees.minimum.amount.value.toString()),
+ symbol = TextReference.Str(selector.fees.minimum.amount.currencySymbol),
+ isSelected = selector.selectedFee == FeeType.SLOW,
+ onSelect = { clickIntents.onFeeSelectorClick(FeeType.SLOW) },
+ )
+ SendSpeedSelectorItem(
+ titleRes = R.string.common_fee_selector_option_market,
+ iconRes = R.drawable.ic_bird_24,
+ amount = TextReference.Str(selector.fees.normal.amount.value.toString()),
+ symbol = TextReference.Str(selector.fees.normal.amount.currencySymbol),
+ isSelected = selector.selectedFee == FeeType.MARKET,
+ onSelect = { clickIntents.onFeeSelectorClick(FeeType.MARKET) },
+ )
+ SendSpeedSelectorItem(
+ titleRes = R.string.common_fee_selector_option_fast,
+ iconRes = R.drawable.ic_hare_24,
+ amount = TextReference.Str(selector.fees.priority.amount.value.toString()),
+ symbol = TextReference.Str(selector.fees.priority.amount.currencySymbol),
+ isSelected = selector.selectedFee == FeeType.FAST,
+ onSelect = { clickIntents.onFeeSelectorClick(FeeType.FAST) },
+ showDivider = selector.fees.normal is Fee.Ethereum,
+ )
+ if (selector.fees.normal is Fee.Ethereum) {
+ SendSpeedSelectorItem(
+ titleRes = R.string.common_fee_selector_option_custom,
+ iconRes = R.drawable.ic_edit_24,
+ isSelected = selector.selectedFee == FeeType.CUSTOM,
+ onSelect = { clickIntents.onFeeSelectorClick(FeeType.CUSTOM) },
+ showDivider = selector.fees.normal !is Fee.Ethereum,
+ )
+ }
+ }
+ is TransactionFee.Single -> {
+ SendSpeedSelectorItem(
+ titleRes = R.string.common_fee_selector_option_market,
+ iconRes = R.drawable.ic_bird_24,
+ isSelected = true,
+ amount = TextReference.Str(selector.fees.normal.amount.value.toString()),
+ symbol = TextReference.Str(selector.fees.normal.amount.currencySymbol),
+ onSelect = { clickIntents.onFeeSelectorClick(FeeType.MARKET) },
+ showDivider = false,
+ )
+ }
+ }
+ }
+ FeeSelectorState.Empty -> Unit
+ }
+ }
+ }
+}
+
+@Composable
+private fun SendSpeedSelectorItemLoading() {
+ Row(modifier = Modifier.fillMaxWidth()) {
+ RectangleShimmer(
+ radius = TangemTheme.dimens.radius3,
+ modifier = Modifier
+ .padding(
+ top = TangemTheme.dimens.spacing18,
+ bottom = TangemTheme.dimens.spacing18,
+ start = TangemTheme.dimens.spacing12,
+ )
+ .size(
+ width = TangemTheme.dimens.size50,
+ height = TangemTheme.dimens.size12,
+ ),
+ )
+ SpacerWMax()
+ RectangleShimmer(
+ radius = TangemTheme.dimens.radius3,
+ modifier = Modifier
+ .padding(
+ top = TangemTheme.dimens.spacing18,
+ bottom = TangemTheme.dimens.spacing18,
+ end = TangemTheme.dimens.spacing12,
+ )
+ .size(
+ width = TangemTheme.dimens.size90,
+ height = TangemTheme.dimens.size12,
+ ),
+ )
+ }
+}
+
+@Composable
+private fun SendSpeedSelectorItem(
+ @StringRes titleRes: Int,
+ @DrawableRes iconRes: Int,
+ onSelect: () -> Unit,
+ modifier: Modifier = Modifier,
+ amount: TextReference? = null,
+ symbol: TextReference? = null,
+ isSelected: Boolean = false,
+ showDivider: Boolean = true,
+) {
+ val iconTint by animateColorAsState(
+ targetValue = if (isSelected) {
+ TangemTheme.colors.icon.accent
+ } else {
+ TangemTheme.colors.icon.informative
+ },
+ label = "Selector icon tint change",
+ )
+
+ val textStyle = if (isSelected) {
+ TangemTheme.typography.subtitle2
+ } else {
+ TangemTheme.typography.body2
+ }
+
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .clickable { onSelect() },
+ ) {
+ Row(modifier = Modifier.fillMaxWidth()) {
+ Icon(
+ painter = painterResource(iconRes),
+ tint = iconTint,
+ contentDescription = null,
+ modifier = Modifier
+ .padding(
+ start = TangemTheme.dimens.spacing12,
+ top = TangemTheme.dimens.spacing12,
+ bottom = TangemTheme.dimens.spacing12,
+ ),
+ )
+ Text(
+ text = stringResource(titleRes),
+ style = textStyle,
+ color = TangemTheme.colors.text.primary1,
+ modifier = Modifier
+ .padding(
+ start = TangemTheme.dimens.spacing8,
+ top = TangemTheme.dimens.spacing14,
+ bottom = TangemTheme.dimens.spacing14,
+ ),
+ )
+ if (amount != null && symbol != null) {
+ SelectorValueContent(
+ amount = amount,
+ symbol = symbol,
+ textStyle = textStyle,
+ )
+ }
+ }
+ if (showDivider) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(TangemTheme.dimens.size1)
+ .padding(horizontal = TangemTheme.dimens.spacing12)
+ .background(TangemTheme.colors.stroke.primary)
+ .align(Alignment.BottomCenter),
+ )
+ }
+ }
+}
+
+@Composable
+private fun RowScope.SelectorValueContent(amount: TextReference, symbol: TextReference, textStyle: TextStyle) {
+ Text(
+ text = amount.resolveReference(),
+ style = textStyle,
+ color = TangemTheme.colors.text.primary1,
+ textAlign = TextAlign.End,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ modifier = Modifier
+ .weight(1f)
+ .padding(
+ start = TangemTheme.dimens.spacing4,
+ top = TangemTheme.dimens.spacing14,
+ bottom = TangemTheme.dimens.spacing14,
+ ),
+ )
+ Text(
+ text = symbol.resolveReference(),
+ style = textStyle,
+ color = TangemTheme.colors.text.primary1,
+ modifier = Modifier
+ .padding(
+ start = TangemTheme.dimens.spacing1,
+ end = TangemTheme.dimens.spacing12,
+ top = TangemTheme.dimens.spacing14,
+ bottom = TangemTheme.dimens.spacing14,
+ ),
+ )
+}
+
+//region preview
+@Preview
+@Composable
+private fun FeeSelectorPreview_Light() {
+ TangemTheme {
+ SendSpeedSelectorItemLoading()
+ }
+}
+
+@Preview
+@Composable
+private fun FeeSelectorPreview_Dark() {
+ TangemTheme(isDark = true) {
+ SendSpeedSelectorItemLoading()
+ }
+}
+//endregion
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSubtract.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSubtract.kt
new file mode 100644
index 0000000000..fac746da5b
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSubtract.kt
@@ -0,0 +1,66 @@
+package com.tangem.features.send.impl.presentation.ui.fee
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.tangem.core.ui.components.TangemSwitch
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.features.send.impl.R
+import com.tangem.features.send.impl.presentation.ui.common.FooterContainer
+import kotlinx.coroutines.flow.StateFlow
+
+@Composable
+internal fun SendSpeedSubtract(
+ receivingAmount: StateFlow,
+ isSubtract: StateFlow,
+ onSelectClick: (Boolean) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val isSelected = isSubtract.collectAsStateWithLifecycle()
+ val footer = receivingAmount.collectAsStateWithLifecycle()
+
+ val footerText = if (isSelected.value) {
+ stringResource(R.string.send_amount_substract_footer, footer.value)
+ } else {
+ null
+ }
+
+ FooterContainer(
+ footer = footerText,
+ modifier = modifier,
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(TangemTheme.shapes.roundedCornersXMedium)
+ .background(TangemTheme.colors.background.action)
+ .padding(
+ vertical = TangemTheme.dimens.spacing16,
+ horizontal = TangemTheme.dimens.spacing20,
+ ),
+ ) {
+ Text(
+ text = stringResource(R.string.send_amount_substract),
+ style = TangemTheme.typography.subtitle1,
+ color = TangemTheme.colors.text.primary1,
+ modifier = Modifier
+ .padding(end = TangemTheme.dimens.spacing12),
+ )
+ TangemSwitch(
+ checked = isSelected.value,
+ onCheckedChange = onSelectClick,
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFields.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFields.kt
index 3a2ed57e1a..f2a792f03e 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFields.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFields.kt
@@ -6,6 +6,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@@ -175,7 +176,9 @@ internal fun TextFieldWithInfo(
modifier: Modifier = Modifier,
info: TextReference? = null,
footer: String? = null,
+ isSingleLine: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
+ keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
) {
FooterContainer(
footer = footer,
@@ -206,6 +209,8 @@ internal fun TextFieldWithInfo(
value = value,
onValueChange = onValueChange,
visualTransformation = visualTransformation,
+ singleLine = isSingleLine,
+ keyboardOptions = keyboardOptions,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing6)
.weight(1f),
@@ -284,15 +289,17 @@ private fun SimpleTextField(
placeholder: TextReference? = null,
singleLine: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
+ keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
) {
val focusRequester = remember { FocusRequester() }
BasicTextField(
value = value,
onValueChange = onValueChange,
- textStyle = TangemTheme.typography.body2,
+ textStyle = TangemTheme.typography.body2.copy(color = TangemTheme.colors.text.primary1),
cursorBrush = SolidColor(TangemTheme.colors.text.primary1),
singleLine = singleLine,
visualTransformation = visualTransformation,
+ keyboardOptions = keyboardOptions,
decorationBox = { textValue ->
Box {
if (value.isBlank() && placeholder != null) {
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt
index 03740cb51e..35df0a6614 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt
@@ -1,5 +1,7 @@
package com.tangem.features.send.impl.presentation.viewmodel
+import com.tangem.features.send.impl.presentation.state.fee.FeeType
+
interface SendClickIntents {
fun onBackClick()
@@ -23,4 +25,12 @@ interface SendClickIntents {
fun onRecipientMemoValueChange(value: String)
// endregion
+
+ // region Fee
+ fun onFeeSelectorClick(feeType: FeeType)
+
+ fun onCustomFeeValueChange(index: Int, value: String)
+
+ fun onSubtractSelect(value: Boolean)
+ // endregion
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
index 2bc9bb638a..3e8bc7477f 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
@@ -9,13 +9,16 @@ import arrow.core.getOrElse
import com.tangem.blockchain.blockchains.xrp.XrpAddressService
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.address.Address
+import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.Provider
+import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
+import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
@@ -27,7 +30,10 @@ import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
import com.tangem.features.send.impl.presentation.state.SendStateFactory
import com.tangem.features.send.impl.presentation.state.SendUiState
+import com.tangem.features.send.impl.presentation.state.SendUiStateType
import com.tangem.features.send.impl.presentation.state.StateRouter
+import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
+import com.tangem.features.send.impl.presentation.state.fee.FeeType
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
@@ -37,6 +43,7 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
+import java.math.BigDecimal
import javax.inject.Inject
import kotlin.properties.Delegates
@@ -50,6 +57,7 @@ internal class SendViewModel @Inject constructor(
private val getWalletsUseCase: GetWalletsUseCase,
private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase,
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
+ private val getFeeUseCase: GetFeeUseCase,
private val walletManagersFacade: WalletManagersFacade,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, SendClickIntents {
@@ -84,11 +92,13 @@ internal class SendViewModel @Inject constructor(
private var balanceJobHolder = JobHolder()
private var recipientsJobHolder = JobHolder()
private var walletAddressesJobHolder = JobHolder()
+ private var feeJobHolder = JobHolder()
override fun onCreate(owner: LifecycleOwner) {
getWalletAddresses()
subscribeOnCurrencyStatusUpdates(owner)
getWalletsAndRecent()
+ getFee()
}
fun setRouter(router: StateRouter) {
@@ -203,6 +213,38 @@ internal class SendViewModel @Inject constructor(
}
}
+ private fun getFee() {
+ viewModelScope.launch(dispatchers.main) {
+ uiState.currentState
+ .filter { it == SendUiStateType.Fee }
+ .onEach {
+ val amountState = uiState.amountState ?: return@onEach
+ val recipientState = uiState.recipientState ?: return@onEach
+
+ stateFactory.onFeeOnLoadingState()
+ getFeeUseCase.invoke(
+ amount = amountState.amountTextField.value.value.toBigDecimal(),
+ destination = recipientState.addressTextField.value.value,
+ userWalletId = userWalletId,
+ cryptoCurrency = cryptoCurrency,
+ )
+ .conflate()
+ .distinctUntilChanged()
+ .onEach { maybeFee ->
+ maybeFee.fold(
+ ifRight = {
+ stateFactory.onFeeOnLoadedState(it)
+ },
+ ifLeft = {
+ // TODO add error handling
+ },
+ )
+ }
+ .launchIn(viewModelScope)
+ }.launchIn(viewModelScope)
+ }.saveIn(feeJobHolder)
+ }
+
private fun getWalletAddresses() {
viewModelScope.launch(dispatchers.io) {
walletAddresses = walletManagersFacade.getAddresses(
@@ -268,6 +310,62 @@ internal class SendViewModel @Inject constructor(
}
// endregion
+ //region fee
+ override fun onFeeSelectorClick(feeType: FeeType) {
+ stateFactory.onFeeSelectedState(feeType)
+ updateReceiveAmount()
+ }
+
+ override fun onCustomFeeValueChange(index: Int, value: String) {
+ uiState.feeState?.apply {
+ (feeSelectorState.value as? FeeSelectorState.Content)?.let { feeSelector ->
+ feeSelector.customValues.update {
+ it.toMutableList().apply {
+ set(index, it[index].copy(value = value))
+ }
+ }
+ updateReceiveAmount()
+ }
+ }
+ }
+
+ override fun onSubtractSelect(value: Boolean) {
+ uiState.feeState?.isSubtract?.update { value }
+ if (value) {
+ updateReceiveAmount()
+ }
+ }
+
+ private fun updateReceiveAmount() {
+ uiState.feeState?.receivedAmount?.update {
+ BigDecimalFormatter.formatCryptoAmount(
+ cryptoAmount = calculateReceiveAmount(),
+ cryptoCurrency = cryptoCurrency.symbol,
+ decimals = cryptoCurrency.decimals,
+ )
+ }
+ }
+
+ private fun calculateReceiveAmount(): BigDecimal {
+ val feeState = uiState.feeState?.feeSelectorState?.value as? FeeSelectorState.Content ?: return BigDecimal.ZERO
+ val amount = uiState.amountState?.amountTextField?.value ?: return BigDecimal.ZERO
+
+ val fee = when (val selectedFee = feeState.fees) {
+ is TransactionFee.Choosable -> {
+ when (feeState.selectedFee) {
+ FeeType.SLOW -> selectedFee.minimum.amount.value
+ FeeType.MARKET -> selectedFee.normal.amount.value
+ FeeType.FAST -> selectedFee.priority.amount.value
+ FeeType.CUSTOM -> feeState.customValues.value.firstOrNull()?.value?.let { BigDecimal(it) }
+ }
+ }
+ is TransactionFee.Single -> selectedFee.normal.amount.value
+ } ?: BigDecimal.ZERO
+
+ return BigDecimal(amount.value).minus(fee)
+ }
+ //endregion
+
companion object {
private const val XRP_X_ADDRESS = 'X'
private const val DEFAULT_VALUE = "0.00"
diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml
index 86e6d89da1..eed86ea134 100644
--- a/gradle/dependencies.toml
+++ b/gradle/dependencies.toml
@@ -88,7 +88,7 @@ spr-client = "3.6.2"
# endregion Other libraries
# region Tangem
-tangemBlockchainSdk = "develop-384"
+tangemBlockchainSdk = "develop-387"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "develop-310"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2bda17e30f..59ac1ba906 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -127,6 +127,7 @@ include(":domain:app-theme")
include(":domain:app-theme:models")
include(":domain:balance-hiding")
include(":domain:balance-hiding:models")
+include(":domain:transaction")
// endregion Domain modules