diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index 6fe946df8b..190b4f48ad 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -98,6 +98,7 @@
Медленно
Скорость и комиссия
Получить адреса
+ К провайдеру
Импортировать
Нравится
Заблокирован
@@ -449,6 +450,8 @@
Комиссия превышает баланс
Комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить 0.01.
Увеличение комиссии
+ Установлена высокая комиссия
+ Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна.
Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению
Недопустимая сумма
Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %1$s.
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 6045fd4bd4..4aea85ef4c 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -95,6 +95,7 @@
Market
Slow
Speed and fee
+ Go to provider
Get addresses
Import
Learn & Earn
@@ -223,7 +224,6 @@
List of all tokens added to your wallet
Fetching best rates...
Floating rate
- Go to provider
Provider
Best rate
Available from %s
@@ -473,6 +473,8 @@
Fee exceeds balance
The fee for transferring the entire balance is higher. To reduce the commission, you can leave 0.01.
Fee is increased
+ Custom fee is high
+ The commission amount is %s times the recommended amount. Make sure that the custom settings are correct.
The included commission exceeds the transfer amount, leading to a negative value
Invalid amount
The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %1$s.
diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletAddressServiceRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletAddressServiceRepository.kt
index 77ca9b019c..689f2e9c63 100644
--- a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletAddressServiceRepository.kt
+++ b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletAddressServiceRepository.kt
@@ -12,13 +12,13 @@ class DefaultWalletAddressServiceRepository(
) : WalletAddressServiceRepository {
override suspend fun validate(userWalletId: UserWalletId, network: Network, address: String): Boolean {
val blockchain = Blockchain.fromId(network.id.value)
- val walletManager = walletManagersFacade.getOrCreateWalletManager(
- userWalletId = userWalletId,
- blockchain = blockchain,
- derivationPath = network.derivationPath.value,
- ) ?: return false
return if (blockchain.isNear()) {
+ val walletManager = walletManagersFacade.getOrCreateWalletManager(
+ userWalletId = userWalletId,
+ blockchain = blockchain,
+ derivationPath = network.derivationPath.value,
+ ) ?: return false
(walletManager as? NearWalletManager)?.validateAddress(address) ?: false
} else {
blockchain.validateAddress(address)
diff --git a/features/send/impl/build.gradle.kts b/features/send/impl/build.gradle.kts
index d45ddc152e..69d2d12a3f 100644
--- a/features/send/impl/build.gradle.kts
+++ b/features/send/impl/build.gradle.kts
@@ -61,6 +61,7 @@ dependencies {
/** Feature modules */
implementation(projects.features.send.api)
+ implementation(projects.features.tokendetails.api)
/** DI */
implementation(deps.hilt.android)
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt
index c72ca92ae7..41ddca52e6 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt
@@ -1,9 +1,14 @@
package com.tangem.features.send.impl.navigation
+import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
+import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.ReduxNavController
+import com.tangem.domain.tokens.model.CryptoCurrency
+import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.send.impl.presentation.SendFragment
+import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
internal class DefaultSendRouter(
private val reduxNavController: ReduxNavController,
@@ -14,4 +19,16 @@ internal class DefaultSendRouter(
override fun openUrl(url: String) {
reduxNavController.navigate(NavigationAction.OpenUrl(url = url))
}
+
+ override fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) {
+ reduxNavController.navigate(
+ action = NavigationAction.NavigateTo(
+ screen = AppScreen.WalletDetails,
+ bundle = bundleOf(
+ TokenDetailsRouter.USER_WALLET_ID_KEY to userWalletId.stringValue,
+ TokenDetailsRouter.CRYPTO_CURRENCY_KEY to currency,
+ ),
+ ),
+ )
+ }
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt
index b7cd7f934a..1cba26003d 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt
@@ -1,9 +1,14 @@
package com.tangem.features.send.impl.navigation
+import com.tangem.domain.tokens.model.CryptoCurrency
+import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.send.api.navigation.SendRouter
interface InnerSendRouter : SendRouter {
/** Open website by [url] */
fun openUrl(url: String)
+
+ /** Open token details screen by [userWalletId] and [currency] */
+ fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency)
}
\ No newline at end of file
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 0cdd57f8a2..1f03dddf54 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
@@ -5,6 +5,7 @@ import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.txhistory.models.TxHistoryItem
@@ -12,21 +13,23 @@ 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.fee.*
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.fee.calculateReceiveAmount
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientListConverter
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.features.send.impl.presentation.viewmodel.isNotAddressInWallet
import com.tangem.features.send.impl.presentation.viewmodel.validateMemo
import com.tangem.utils.Provider
+import com.tangem.utils.isNullOrZero
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.update
+import java.math.BigDecimal
+@Suppress("LongParameterList")
internal class SendStateFactory(
private val clickIntents: SendClickIntents,
private val currentStateProvider: Provider,
@@ -34,6 +37,7 @@ internal class SendStateFactory(
private val walletAddressesProvider: Provider>,
private val appCurrencyProvider: Provider,
private val cryptoCurrencyStatusProvider: Provider,
+ coinCryptoCurrencyStatusProvider: Provider,
) {
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
@@ -47,6 +51,12 @@ internal class SendStateFactory(
)
}
+ private val feeNotificationFactory = FeeNotificationFactory(
+ coinCryptoCurrencyStatusProvider = coinCryptoCurrencyStatusProvider,
+ userWalletProvider = userWalletProvider,
+ clickIntents = clickIntents,
+ )
+
private val amountStateConverter by lazy {
SendAmountStateConverter(
appCurrencyProvider = appCurrencyProvider,
@@ -196,28 +206,128 @@ 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
+ fun onFeeOnLoadingState(): SendUiState {
+ val state = currentStateProvider()
+ val feeState = state.feeState ?: return state
+ val feeSelectorState = FeeSelectorState.Loading
+ return state.copy(
+ feeState = feeState.copy(
+ feeSelectorState = feeSelectorState,
+ notifications = persistentListOf(),
+ isPrimaryButtonEnabled = feeSelectorState.isPrimaryButtonEnabled(),
+ ),
+ )
+ }
+
+ fun onFeeOnLoadedState(fees: TransactionFee, isMaxAmount: Boolean): SendUiState {
+ val state = currentStateProvider()
+ val feeState = state.feeState ?: return state
+ val feeSelectorState = FeeSelectorState.Content(
+ fees = fees,
+ customValues = customFeeFieldConverter.convert(fees.normal),
+ )
+ val updatedState = feeState.copy(
+ feeSelectorState = feeSelectorState,
+ receivedAmount = feeSelectorState.updateReceiveAmount(),
+ isSubtract = isMaxAmount,
+ )
+ return state.copy(
+ feeState = updatedState.copy(
+ notifications = feeNotificationFactory(feeState = updatedState),
+ isPrimaryButtonEnabled = feeSelectorState.isPrimaryButtonEnabled(),
+ ),
+ )
+ }
+
+ fun onFeeSelectedState(feeType: FeeType): SendUiState {
+ val state = currentStateProvider()
+ val feeState = state.feeState ?: return state
+ val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
+ val updatedFeeSelectorState = feeSelectorState.copy(selectedFee = feeType)
+ val updatedState = feeState.copy(
+ receivedAmount = updatedFeeSelectorState.updateReceiveAmount(),
+ feeSelectorState = updatedFeeSelectorState,
+ isPrimaryButtonEnabled = updatedFeeSelectorState.isPrimaryButtonEnabled(),
+ )
+
+ return state.copy(
+ feeState = updatedState.copy(
+ notifications = feeNotificationFactory(feeState = updatedState),
+ ),
+ )
+ }
+
+ fun onCustomFeeValueChange(index: Int, value: String): SendUiState {
+ val state = currentStateProvider()
+ val feeState = state.feeState ?: return state
+ val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
+ val updatedFeeSelectorState = feeSelectorState.copy(
+ customValues = feeSelectorState.customValues.toMutableList().apply {
+ set(index, feeSelectorState.customValues[index].copy(value = value))
+ }.toImmutableList(),
+ )
+ val updatedState = feeState.copy(
+ feeSelectorState = updatedFeeSelectorState,
+ receivedAmount = updatedFeeSelectorState.updateReceiveAmount(),
+ isPrimaryButtonEnabled = updatedFeeSelectorState.isPrimaryButtonEnabled(),
+ )
+ return state.copy(
+ feeState = updatedState.copy(
+ notifications = feeNotificationFactory(feeState = updatedState),
+ ),
+ )
+ }
+
+ fun onSubtractSelect(value: Boolean): SendUiState {
+ val state = currentStateProvider()
+ val feeState = state.feeState ?: return state
+ val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
+ val updatedState = feeState.copy(
+ isSubtract = value,
+ receivedAmount = if (value) {
+ feeSelectorState.updateReceiveAmount()
+ } else {
+ feeState.receivedAmount
+ },
+ )
+ return state.copy(
+ feeState = updatedState.copy(
+ notifications = feeNotificationFactory(feeState = updatedState),
+ ),
+ )
+ }
+
+ private fun FeeSelectorState.isPrimaryButtonEnabled(): Boolean {
+ return when (this) {
+ is FeeSelectorState.Loading -> false
+ is FeeSelectorState.Content -> {
+ val choosableFee = fees as? TransactionFee.Choosable
+ val customValue = customValues.firstOrNull()?.value?.toBigDecimalOrNull()
+ val balance = cryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
+ val fee = when (selectedFee) {
+ FeeType.SLOW -> choosableFee?.minimum?.amount?.value
+ FeeType.MARKET -> fees.normal.amount.value
+ FeeType.FAST -> choosableFee?.priority?.amount?.value
+ FeeType.CUSTOM -> customValue
+ } ?: BigDecimal.ZERO
+
+ val isNotEmptyCustom = !customValue.isNullOrZero() && selectedFee == FeeType.CUSTOM
+ val isNotCustom = selectedFee != FeeType.CUSTOM
+ fee < balance && (isNotEmptyCustom || isNotCustom)
+ }
}
}
+
+ private fun FeeSelectorState.Content.updateReceiveAmount(): String {
+ val cryptoCurrency = cryptoCurrencyStatusProvider().currency
+ return BigDecimalFormatter.formatCryptoAmount(
+ cryptoAmount = calculateReceiveAmount(currentStateProvider()),
+ cryptoCurrency = cryptoCurrency.symbol,
+ decimals = cryptoCurrency.decimals,
+ )
+ }
//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 b7eb3da888..3ff13c540a 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
@@ -9,9 +9,12 @@ 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.fee.SendFeeNotification
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
+import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentList
+import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
/**
@@ -62,9 +65,11 @@ internal sealed class SendStates {
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(""),
+ val feeSelectorState: FeeSelectorState = FeeSelectorState.Loading,
+ val isSubtract: Boolean = false,
+ val receivedAmount: String = "",
+ val notifications: ImmutableList = persistentListOf(),
+ val isPrimaryButtonEnabled: Boolean = false,
) : SendStates()
/** Send state */
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt
new file mode 100644
index 0000000000..263f4df2c0
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt
@@ -0,0 +1,26 @@
+package com.tangem.features.send.impl.presentation.state.fee
+
+import com.tangem.blockchain.common.transaction.TransactionFee
+import com.tangem.features.send.impl.presentation.state.SendUiState
+import java.math.BigDecimal
+
+/**
+ * Calculate receiving amount when fee is subtracted from sending amount
+ */
+internal fun FeeSelectorState.Content.calculateReceiveAmount(uiState: SendUiState): BigDecimal {
+ val amount = uiState.amountState?.amountTextField?.value ?: return BigDecimal.ZERO
+
+ val fee = when (fees) {
+ is TransactionFee.Choosable -> {
+ when (selectedFee) {
+ FeeType.SLOW -> fees.minimum.amount.value
+ FeeType.MARKET -> fees.normal.amount.value
+ FeeType.FAST -> fees.priority.amount.value
+ FeeType.CUSTOM -> customValues.firstOrNull()?.value?.let { BigDecimal(it.ifEmpty { "0" }) }
+ }
+ }
+ is TransactionFee.Single -> fees.normal.amount.value
+ } ?: BigDecimal.ZERO
+
+ return BigDecimal(amount).minus(fee)
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt
new file mode 100644
index 0000000000..f9706b0d17
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt
@@ -0,0 +1,96 @@
+package com.tangem.features.send.impl.presentation.state.fee
+
+import com.tangem.blockchain.common.transaction.TransactionFee
+import com.tangem.core.ui.extensions.networkIconResId
+import com.tangem.domain.tokens.model.CryptoCurrencyStatus
+import com.tangem.domain.wallets.models.UserWallet
+import com.tangem.features.send.impl.presentation.state.SendStates
+import com.tangem.features.send.impl.presentation.state.fields.SendTextField
+import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
+import com.tangem.utils.Provider
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.toImmutableList
+import java.math.BigDecimal
+
+internal class FeeNotificationFactory(
+ private val coinCryptoCurrencyStatusProvider: Provider,
+ private val userWalletProvider: Provider,
+ private val clickIntents: SendClickIntents,
+) {
+
+ operator fun invoke(feeState: SendStates.FeeState): ImmutableList {
+ val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return persistentListOf()
+ val customFee = feeSelectorState.customValues
+ val selectedFee = feeSelectorState.selectedFee
+
+ return buildList {
+ addTooLowNotification(feeSelectorState.fees, selectedFee, customFee)
+ addTooHighNotification(feeSelectorState.fees, selectedFee, customFee)
+ addFeeCoverageNotification()
+ addExceedsBalanceNotification(feeSelectorState)
+ }.toImmutableList()
+ }
+
+ private fun MutableList.addTooLowNotification(
+ transactionFee: TransactionFee,
+ selectedFee: FeeType,
+ customFee: List,
+ ) {
+ val multipleFees = transactionFee as? TransactionFee.Choosable ?: return
+ val minimumValue = multipleFees.minimum.amount.value ?: return
+ val customValue = customFee.firstOrNull()?.value?.toBigDecimalOrNull() ?: return
+ if (selectedFee == FeeType.CUSTOM && minimumValue > customValue) {
+ add(SendFeeNotification.Informational.TooLow)
+ }
+ }
+
+ private fun MutableList.addTooHighNotification(
+ transactionFee: TransactionFee,
+ selectedFee: FeeType,
+ customFee: List,
+ ) {
+ val multipleFees = transactionFee as? TransactionFee.Choosable ?: return
+ val highValue = multipleFees.priority.amount.value ?: return
+ val customValue = customFee.firstOrNull()?.value?.toBigDecimalOrNull() ?: return
+ val diff = customValue / highValue
+ if (selectedFee == FeeType.CUSTOM && diff > FEE_MAX_DIFF) {
+ add(SendFeeNotification.Warning.TooHigh(diff.toInt().toString()))
+ }
+ }
+
+ private fun MutableList.addFeeCoverageNotification() {
+ // TODO add fee coverage condition [REDACTED_JIRA]
+ add(SendFeeNotification.Warning.NetworkCoverage)
+ }
+
+ private fun MutableList.addExceedsBalanceNotification(
+ feeSelectorState: FeeSelectorState.Content,
+ ) {
+ val coinCryptoCurrency = coinCryptoCurrencyStatusProvider()
+ val choosableFee = feeSelectorState.fees as? TransactionFee.Choosable
+ val fee = when (feeSelectorState.selectedFee) {
+ FeeType.SLOW -> choosableFee?.minimum?.amount?.value
+ FeeType.MARKET -> feeSelectorState.fees.normal.amount.value
+ FeeType.FAST -> choosableFee?.priority?.amount?.value
+ FeeType.CUSTOM -> feeSelectorState.customValues.firstOrNull()?.value?.toBigDecimalOrNull()
+ } ?: return
+
+ if (fee > coinCryptoCurrency.value.amount) {
+ add(
+ SendFeeNotification.Error.ExceedsBalance(
+ coinCryptoCurrency.currency.networkIconResId,
+ ) {
+ clickIntents.onTokenDetailsClick(
+ userWalletProvider().walletId,
+ coinCryptoCurrency.currency,
+ )
+ },
+ )
+ }
+ }
+
+ companion object {
+ private val FEE_MAX_DIFF = BigDecimal(5)
+ }
+}
\ 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
index f920348ac8..c5a392c4a9 100644
--- 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
@@ -3,19 +3,18 @@ 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
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
@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()),
+ val customValues: ImmutableList = persistentListOf(),
) : FeeSelectorState()
}
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
index 858fc11a49..ff9da4a71f 100644
--- 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
@@ -11,15 +11,16 @@ import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
internal class SendFeeCustomFieldConverter(
private val clickIntents: SendClickIntents,
private val appCurrencyProvider: Provider,
-) : Converter>> {
+) : Converter> {
- override fun convert(value: Fee): MutableStateFlow> {
- val ethereumFee = value as? Fee.Ethereum ?: return MutableStateFlow(emptyList())
+ override fun convert(value: Fee): ImmutableList {
+ val ethereumFee = value as? Fee.Ethereum ?: return persistentListOf()
val appCurrency = appCurrencyProvider()
val maxFeeFiat = BigDecimalFormatter.formatFiatAmount(
@@ -28,32 +29,30 @@ internal class SendFeeCustomFieldConverter(
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),
+ return persistentListOf(
+ SendTextField.CustomFee(
+ value = ethereumFee.amount.value.toString(),
+ onValueChange = { clickIntents.onCustomFeeValueChange(0, it) },
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next,
+ keyboardType = KeyboardType.Number,
),
- SendTextField.CustomFee(
- value = ethereumFee.gasPrice.toString(),
- onValueChange = { clickIntents.onCustomFeeValueChange(1, 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,
- ),
+ ),
+ SendTextField.CustomFee(
+ value = ethereumFee.gasLimit.toString(),
+ onValueChange = { clickIntents.onCustomFeeValueChange(2, it) },
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Done,
+ keyboardType = KeyboardType.Number,
),
),
)
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeNotification.kt
new file mode 100644
index 0000000000..8763cf5cc2
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeNotification.kt
@@ -0,0 +1,78 @@
+package com.tangem.features.send.impl.presentation.state.fee
+
+import androidx.compose.runtime.Immutable
+import com.tangem.core.ui.components.notifications.NotificationConfig
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.extensions.wrappedList
+import com.tangem.features.send.impl.R
+
+@Immutable
+sealed class SendFeeNotification(val config: NotificationConfig) {
+
+ sealed class Informational(
+ val title: TextReference,
+ val subtitle: TextReference,
+ ) : SendFeeNotification(
+ config = NotificationConfig(
+ title = title,
+ subtitle = subtitle,
+ iconResId = R.drawable.ic_alert_circle_24,
+ ),
+ ) {
+ object TooLow : Informational(
+ title = resourceReference(id = R.string.send_notification_transaction_delay_title),
+ subtitle = resourceReference(id = R.string.send_notification_transaction_delay_text),
+ )
+ }
+
+ sealed class Warning(
+ val title: TextReference,
+ val subtitle: TextReference,
+ ) : SendFeeNotification(
+ config = NotificationConfig(
+ title = title,
+ subtitle = subtitle,
+ iconResId = R.drawable.img_attention_20,
+ ),
+ ) {
+ data class TooHigh(
+ val value: String,
+ ) : Warning(
+ title = resourceReference(id = R.string.send_notification_fee_too_high_title),
+ subtitle = resourceReference(id = R.string.send_notification_fee_too_high_text, wrappedList(value)),
+ )
+
+ object NetworkCoverage : Warning(
+ title = resourceReference(id = R.string.send_network_fee_warning_title),
+ subtitle = resourceReference(id = R.string.send_network_fee_warning_content),
+ )
+ }
+
+ sealed class Error(
+ val title: TextReference,
+ val subtitle: TextReference,
+ val iconResId: Int,
+ val buttonsState: NotificationConfig.ButtonsState,
+ ) : SendFeeNotification(
+ config = NotificationConfig(
+ title = title,
+ subtitle = subtitle,
+ iconResId = iconResId,
+ buttonsState = buttonsState,
+ ),
+ ) {
+ data class ExceedsBalance(
+ val networkIconId: Int,
+ val onClick: () -> Unit,
+ ) : Error(
+ title = resourceReference(id = R.string.send_notification_exceed_fee_title),
+ subtitle = resourceReference(id = R.string.send_notification_exceed_fee_text),
+ iconResId = networkIconId,
+ buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
+ text = resourceReference(R.string.common_go_to_provider),
+ onClick = onClick,
+ ),
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt
index e8118443a5..2f17471513 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt
@@ -14,6 +14,7 @@ import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
@@ -76,16 +77,19 @@ private fun SendPrimaryNavigationButton(uiState: SendUiState, modifier: Modifier
val isSending = uiState.sendState?.isSending?.collectAsStateWithLifecycle()?.value ?: false
val txUrl = uiState.sendState?.txUrl?.collectAsStateWithLifecycle()?.value.orEmpty()
- val (buttonTextId, buttonClick) = getButtonData(
- currentState = currentState,
- isSuccess = isSuccess,
- uiState = uiState,
- )
+ val (buttonTextId, buttonClick) = remember {
+ getButtonData(
+ currentState = currentState,
+ isSuccess = isSuccess,
+ uiState = uiState,
+ )
+ }
- val isButtonEnabled = when (currentState.value) {
- SendUiStateType.Amount -> uiState.amountState?.isPrimaryButtonEnabled ?: false
- SendUiStateType.Recipient -> uiState.recipientState?.isPrimaryButtonEnabled ?: false
- else -> true
+ val isButtonEnabled = remember {
+ isButtonEnabled(
+ currentState = currentState,
+ uiState = uiState,
+ )
}
AnimatedContent(
@@ -181,4 +185,13 @@ private fun getButtonData(
R.string.common_send
} to uiState.clickIntents::onSendClick
}
+}
+
+private fun isButtonEnabled(currentState: State, uiState: SendUiState): Boolean {
+ return when (currentState.value) {
+ SendUiStateType.Amount -> uiState.amountState?.isPrimaryButtonEnabled ?: false
+ SendUiStateType.Recipient -> uiState.recipientState?.isPrimaryButtonEnabled ?: false
+ SendUiStateType.Fee -> uiState.feeState?.isPrimaryButtonEnabled ?: false
+ else -> true
+ }
}
\ No newline at end of file
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
index 98b24927f0..69bc85a649 100644
--- 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
@@ -4,7 +4,6 @@ import androidx.compose.foundation.background
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.visualtransformations.AmountVisualTransformation
@@ -16,20 +15,21 @@ 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.common.FooterContainer
+import kotlinx.collections.immutable.ImmutableList
private const val ETHEREUM_UNIT = "GWEI"
@Composable
internal fun SendCustomFeeEthereum(
- customValues: State>,
+ customValues: ImmutableList,
selectedFee: FeeType,
symbol: String,
modifier: Modifier = Modifier,
) {
- if (selectedFee == FeeType.CUSTOM && customValues.value.isNotEmpty()) {
- val fee = customValues.value[0]
- val gasPrice = customValues.value[1]
- val gasLimit = customValues.value[2]
+ if (selectedFee == FeeType.CUSTOM && customValues.isNotEmpty()) {
+ val fee = customValues[0]
+ val gasPrice = customValues[1]
+ val gasLimit = customValues[2]
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
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
index 595d04491a..4c8c06116d 100644
--- 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
@@ -3,25 +3,32 @@ 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.layout.Arrangement
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.tangem.core.ui.components.notifications.Notification
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.state.fee.SendFeeNotification
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
+import kotlinx.collections.immutable.ImmutableList
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()
+ val feeSendState = state.feeSelectorState
+ val notifications = state.notifications
LazyColumn(
modifier = Modifier
.fillMaxSize()
@@ -39,42 +46,90 @@ internal fun SendSpeedAndFeeContent(state: SendStates.FeeState?, clickIntents: S
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(),
- )
- }
+ notifications(notifications)
+ customFee(
+ feeSendState = feeSendState,
+ cryptoCurrencySymbol = state.cryptoCurrencyStatus.currency.symbol,
+ )
+ subtractButton(
+ feeSendState = feeSendState,
+ receivedAmount = state.receivedAmount,
+ isSubtract = state.isSubtract,
+ clickIntents = clickIntents,
+ )
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+internal fun LazyListScope.notifications(configs: ImmutableList, modifier: Modifier = Modifier) {
+ items(
+ items = configs,
+ key = { it::class.java },
+ contentType = { it::class.java },
+ itemContent = {
+ Notification(
+ config = it.config,
+ modifier = modifier.animateItemPlacement(),
+ containerColor = TangemTheme.colors.button.disabled,
+ iconTint = when (it) {
+ is SendFeeNotification.Informational -> TangemTheme.colors.icon.accent
+ else -> null
+ },
+ )
+ },
+ )
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+internal fun LazyListScope.customFee(
+ feeSendState: FeeSelectorState,
+ cryptoCurrencySymbol: String,
+ modifier: Modifier = Modifier,
+) {
+ item(
+ key = FEE_CUSTOM_KEY,
+ ) {
+ AnimatedVisibility(
+ visible = feeSendState is FeeSelectorState.Content,
+ modifier = modifier
+ .fillMaxWidth()
+ .animateItemPlacement()
+ .background(TangemTheme.colors.background.tertiary),
+ ) {
+ (feeSendState as? FeeSelectorState.Content)?.let { fee ->
+ val customValues = fee.customValues
+ SendCustomFeeEthereum(
+ customValues = customValues,
+ selectedFee = fee.selectedFee,
+ symbol = cryptoCurrencySymbol,
+ )
}
}
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+internal fun LazyListScope.subtractButton(
+ feeSendState: FeeSelectorState,
+ receivedAmount: String,
+ isSubtract: Boolean,
+ clickIntents: SendClickIntents,
+ modifier: Modifier = Modifier,
+) {
+ (feeSendState as? FeeSelectorState.Content)?.let { state ->
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
+ val selectedFeeValue = state.selectedFee
+ val topPadding = if (selectedFeeValue != FeeType.CUSTOM) {
+ TangemTheme.dimens.spacing8
+ } else {
+ TangemTheme.dimens.spacing0
+ }
SendSpeedSubtract(
- receivingAmount = state.receivedAmount,
- isSubtract = state.isSubtract,
+ receivingAmount = receivedAmount,
+ isSubtract = isSubtract,
onSelectClick = clickIntents::onSubtractSelect,
- modifier = Modifier
+ modifier = modifier
.animateItemPlacement()
.padding(
top = topPadding,
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
index de69e011d6..f221817a1d 100644
--- 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
@@ -9,7 +9,6 @@ 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
@@ -36,7 +35,7 @@ import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
@Suppress("LongMethod")
@Composable
internal fun SendSpeedSelector(
- state: State,
+ state: FeeSelectorState,
clickIntents: SendClickIntents,
modifier: Modifier = Modifier,
) {
@@ -50,47 +49,48 @@ internal fun SendSpeedSelector(
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action),
) {
- when (val selector = state.value) {
+ when (state) {
FeeSelectorState.Loading -> {
SendSpeedSelectorItemLoading()
SendSpeedSelectorItemLoading()
SendSpeedSelectorItemLoading()
}
is FeeSelectorState.Content -> {
- when (selector.fees) {
+ when (state.fees) {
is TransactionFee.Choosable -> {
+ val isSelected = state.selectedFee
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,
+ amount = TextReference.Str(state.fees.minimum.amount.value.toString()),
+ symbol = TextReference.Str(state.fees.minimum.amount.currencySymbol),
+ isSelected = isSelected == 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,
+ amount = TextReference.Str(state.fees.normal.amount.value.toString()),
+ symbol = TextReference.Str(state.fees.normal.amount.currencySymbol),
+ isSelected = isSelected == 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,
+ amount = TextReference.Str(state.fees.priority.amount.value.toString()),
+ symbol = TextReference.Str(state.fees.priority.amount.currencySymbol),
+ isSelected = isSelected == FeeType.FAST,
onSelect = { clickIntents.onFeeSelectorClick(FeeType.FAST) },
- showDivider = selector.fees.normal is Fee.Ethereum,
+ showDivider = state.fees.normal is Fee.Ethereum,
)
- if (selector.fees.normal is Fee.Ethereum) {
+ if (state.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,
+ isSelected = isSelected == FeeType.CUSTOM,
onSelect = { clickIntents.onFeeSelectorClick(FeeType.CUSTOM) },
- showDivider = selector.fees.normal !is Fee.Ethereum,
+ showDivider = state.fees.normal !is Fee.Ethereum,
)
}
}
@@ -99,15 +99,14 @@ internal fun SendSpeedSelector(
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),
+ amount = TextReference.Str(state.fees.normal.amount.value.toString()),
+ symbol = TextReference.Str(state.fees.normal.amount.currencySymbol),
onSelect = { clickIntents.onFeeSelectorClick(FeeType.MARKET) },
showDivider = false,
)
}
}
}
- FeeSelectorState.Empty -> Unit
}
}
}
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
index fac746da5b..5fa7355bce 100644
--- 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
@@ -11,25 +11,20 @@ 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,
+ receivingAmount: String,
+ isSubtract: Boolean,
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)
+ val footerText = if (isSubtract) {
+ stringResource(R.string.send_amount_substract_footer, receivingAmount)
} else {
null
}
@@ -58,7 +53,7 @@ internal fun SendSpeedSubtract(
.padding(end = TangemTheme.dimens.spacing12),
)
TangemSwitch(
- checked = isSelected.value,
+ checked = isSubtract,
onCheckedChange = onSelectClick,
)
}
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt
index 9726adff7c..e488e1a3e0 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt
@@ -166,18 +166,18 @@ private fun RecipientBlock(recipientState: SendStates.RecipientState, isSuccess:
@Composable
private fun FeeBlock(feeState: SendStates.FeeState, isSuccess: State, onClick: () -> Unit) {
- val feeSelector =
- feeState.feeSelectorState.collectAsStateWithLifecycle().value as? FeeSelectorState.Content ?: return
- val customValue = feeSelector.customValues.collectAsStateWithLifecycle().value.getOrNull(0)
+ val feeSelector = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
+ val customValue = feeSelector.customValues.getOrNull(0)
+ val selectedFee = feeSelector.selectedFee
val feeValue = formatCryptoAmount(
cryptoCurrency = feeState.cryptoCurrencyStatus.currency,
- cryptoAmount = when (val selectedFee = feeSelector.fees) {
- is TransactionFee.Single -> selectedFee.normal.amount.value
- is TransactionFee.Choosable -> when (feeSelector.selectedFee) {
- FeeType.SLOW -> selectedFee.minimum.amount.value
- FeeType.MARKET -> selectedFee.normal.amount.value
- FeeType.FAST -> selectedFee.priority.amount.value
+ cryptoAmount = when (val fees = feeSelector.fees) {
+ is TransactionFee.Single -> fees.normal.amount.value
+ is TransactionFee.Choosable -> when (selectedFee) {
+ FeeType.SLOW -> fees.minimum.amount.value
+ FeeType.MARKET -> fees.normal.amount.value
+ FeeType.FAST -> fees.priority.amount.value
FeeType.CUSTOM -> customValue?.value.toBigDecimalOrDefault()
}
},
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 d29ab95bee..f79bf242b5 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.domain.tokens.model.CryptoCurrency
+import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.send.impl.presentation.state.fee.FeeType
interface SendClickIntents {
@@ -14,6 +16,8 @@ interface SendClickIntents {
fun onQrCodeScanClick()
+ fun onTokenDetailsClick(userWalletId: UserWalletId, currency: CryptoCurrency)
+
// region Amount
fun onAmountValueChange(value: String)
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 59a21072b0..8797df79a7 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
@@ -18,11 +18,11 @@ import com.tangem.blockchain.common.TransactionExtras
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
-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.GetNetworkCoinStatusUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.utils.convertToAmount
@@ -67,6 +67,7 @@ internal class SendViewModel @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
+ private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getWalletsUseCase: GetWalletsUseCase,
private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase,
@@ -99,12 +100,14 @@ internal class SendViewModel @Inject constructor(
walletAddressesProvider = Provider { walletAddresses },
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
+ coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus },
)
var uiState: SendUiState by mutableStateOf(stateFactory.getInitialState())
private set
private var userWallet: UserWallet by Delegates.notNull()
+ private var coinCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
private var walletAddresses = emptySet()
@@ -131,7 +134,7 @@ internal class SendViewModel @Inject constructor(
getUserWalletUseCase(userWalletId).fold(
ifRight = { wallet ->
userWallet = wallet
- getCurrencyStatusUpdates(owner, wallet)
+ getCurrenciesStatusUpdates(owner, wallet)
},
ifLeft = {
// TODO add error handling
@@ -141,28 +144,54 @@ internal class SendViewModel @Inject constructor(
}
}
- private fun getCurrencyStatusUpdates(owner: LifecycleOwner, wallet: UserWallet) {
+ private fun getCurrenciesStatusUpdates(owner: LifecycleOwner, wallet: UserWallet) {
val isSingleWallet = wallet.scanResponse.walletData?.token != null && !wallet.isMultiCurrency
- getCurrencyStatusUpdatesUseCase(
- userWalletId = userWalletId,
- currencyId = cryptoCurrency.id,
- isSingleWalletWithTokens = isSingleWallet,
- )
- .flowWithLifecycle(owner.lifecycle)
- .conflate()
- .distinctUntilChanged()
- .onEach { either ->
- either.onRight {
- cryptoCurrencyStatus = it
+
+ if (cryptoCurrency is CryptoCurrency.Coin) {
+ getCurrencyStatusUpdates(isSingleWallet = isSingleWallet)
+ .flowWithLifecycle(owner.lifecycle)
+ .onEach { currencyStatus ->
+ currencyStatus.onRight {
+ cryptoCurrencyStatus = it
+ coinCryptoCurrencyStatus = it
+ getWalletsAndRecent()
+ uiState = stateFactory.getReadyState()
+ }
+ }
+ .flowOn(dispatchers.main)
+ .launchIn(viewModelScope)
+ .saveIn(balanceJobHolder)
+ } else {
+ combine(
+ flow = getCoinCurrencyStatusUpdates(isSingleWallet = isSingleWallet),
+ flow2 = getCurrencyStatusUpdates(isSingleWallet = isSingleWallet),
+ ) { coinStatus, currencyStatus ->
+ if (coinStatus.isRight() && currencyStatus.isRight()) {
+ coinStatus.onRight { coinCryptoCurrencyStatus = it }
+ currencyStatus.onRight { cryptoCurrencyStatus = it }
getWalletsAndRecent()
uiState = stateFactory.getReadyState()
}
- }
- .flowOn(dispatchers.main)
- .launchIn(viewModelScope)
- .saveIn(balanceJobHolder)
+ }.flowWithLifecycle(owner.lifecycle)
+ .flowOn(dispatchers.main)
+ .launchIn(viewModelScope)
+ .saveIn(balanceJobHolder)
+ }
}
+ private fun getCoinCurrencyStatusUpdates(isSingleWallet: Boolean) = getNetworkCoinStatusUseCase(
+ userWalletId = userWalletId,
+ networkId = cryptoCurrency.network.id,
+ derivationPath = cryptoCurrency.network.derivationPath,
+ isSingleWalletWithTokens = isSingleWallet,
+ ).conflate().distinctUntilChanged()
+
+ private fun getCurrencyStatusUpdates(isSingleWallet: Boolean) = getCurrencyStatusUpdatesUseCase(
+ userWalletId = userWalletId,
+ currencyId = cryptoCurrency.id,
+ isSingleWalletWithTokens = isSingleWallet,
+ ).conflate().distinctUntilChanged()
+
private fun createSelectedAppCurrencyFlow(): StateFlow {
return getSelectedAppCurrencyUseCase()
.map { maybeAppCurrency ->
@@ -255,10 +284,11 @@ internal class SendViewModel @Inject constructor(
.onEach {
val amountState = uiState.amountState ?: return@onEach
val recipientState = uiState.recipientState ?: return@onEach
+ val amount = amountState.amountTextField.value.toBigDecimal()
- stateFactory.onFeeOnLoadingState()
+ uiState = stateFactory.onFeeOnLoadingState()
getFeeUseCase.invoke(
- amount = amountState.amountTextField.value.toBigDecimal(),
+ amount = amount,
destination = recipientState.addressTextField.value,
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
@@ -268,7 +298,7 @@ internal class SendViewModel @Inject constructor(
.onEach { maybeFee ->
maybeFee.fold(
ifRight = {
- stateFactory.onFeeOnLoadedState(it)
+ uiState = stateFactory.onFeeOnLoadedState(it, true)
},
ifLeft = {
// TODO add error handling
@@ -298,7 +328,10 @@ internal class SendViewModel @Inject constructor(
override fun onQrCodeScanClick() {
// TODO Add QR code scanning
}
- // endregion
+
+ override fun onTokenDetailsClick(userWalletId: UserWalletId, currency: CryptoCurrency) =
+ innerRouter.openTokenDetails(userWalletId, currency)
+// endregion
// region amount state clicks
override fun onCurrencyChangeClick(isFiat: Boolean) {
@@ -318,7 +351,7 @@ internal class SendViewModel @Inject constructor(
}
onAmountValueChange(amount?.toPlainString() ?: DEFAULT_VALUE)
}
- // endregion
+// endregion
// region recipient state clicks
override fun onRecipientAddressValueChange(value: String) {
@@ -362,63 +395,21 @@ internal class SendViewModel @Inject constructor(
}
return false
}
- // endregion
+// endregion
//region fee
override fun onFeeSelectorClick(feeType: FeeType) {
- stateFactory.onFeeSelectedState(feeType)
- updateReceiveAmount()
+ uiState = stateFactory.onFeeSelectedState(feeType)
}
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()
- }
- }
+ uiState = stateFactory.onCustomFeeValueChange(index, value)
}
override fun onSubtractSelect(value: Boolean) {
- uiState.feeState?.isSubtract?.update { value }
- if (value) {
- updateReceiveAmount()
- }
+ uiState = stateFactory.onSubtractSelect(value)
}
-
- 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).minus(fee)
- }
- //endregion
+//endregion
// region send state clicks
override fun onSendClick() {
@@ -435,7 +426,7 @@ internal class SendViewModel @Inject constructor(
val sendState = uiState.sendState ?: return
val amount = uiState.amountState?.amountTextField?.value ?: return
val recipient = uiState.recipientState?.addressTextField?.value ?: return
- val feeState = uiState.feeState?.feeSelectorState?.value as? FeeSelectorState.Content ?: return
+ val feeState = uiState.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return
val memo = uiState.recipientState?.memoTextField?.value
val fee = getFee(feeState) ?: return
@@ -487,7 +478,7 @@ internal class SendViewModel @Inject constructor(
FeeType.MARKET -> selectedFee.normal
FeeType.FAST -> selectedFee.priority
FeeType.CUSTOM -> {
- val feeAmount = feeState.customValues.value.firstOrNull()?.value
+ val feeAmount = feeState.customValues.firstOrNull()?.value
?.let { BigDecimal(it) } ?: return null
Fee.Common(feeAmount.convertToAmount(cryptoCurrency))
}
@@ -517,7 +508,7 @@ internal class SendViewModel @Inject constructor(
)
}
}
- // endregion
+// endregion
private fun getMemoExtras(networkId: String, memo: String?): TransactionExtras? {
val blockchain = Blockchain.fromId(networkId)
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt
index 1da8fd0672..ac825983b8 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt
@@ -16,7 +16,7 @@ internal sealed class ExchangeStatusNotifications(val config: NotificationConfig
subtitle = TextReference.Res(R.string.express_exchange_notification_verification_text),
iconResId = R.drawable.ic_alert_triangle_20,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
- text = TextReference.Res(R.string.express_go_to_provider),
+ text = TextReference.Res(R.string.common_go_to_provider),
onClick = onGoToProviderClick,
),
),
@@ -30,7 +30,7 @@ internal sealed class ExchangeStatusNotifications(val config: NotificationConfig
subtitle = TextReference.Res(R.string.express_exchange_notification_failed_text),
iconResId = R.drawable.ic_alert_circle_24,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
- text = TextReference.Res(R.string.express_go_to_provider),
+ text = TextReference.Res(R.string.common_go_to_provider),
onClick = onGoToProviderClick,
),
),
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt
index 9d44f11a9e..f5196025aa 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt
@@ -69,7 +69,7 @@ internal fun ExchangeStatusBlock(
.padding(end = TangemTheme.dimens.spacing2),
)
Text(
- text = stringResource(id = R.string.express_go_to_provider),
+ text = stringResource(id = R.string.common_go_to_provider),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)