diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt
index b8e9b2e458..4f32eee854 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt
@@ -2,8 +2,8 @@ package com.tangem.tap.common.analytics.converters
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.common.core.TangemSdkError
+import com.tangem.domain.demo.DemoTransactionSender
import com.tangem.tap.common.analytics.events.AnalyticsParam
-import com.tangem.tap.features.demo.DemoTransactionSender
import com.tangem.utils.converter.Converter
/**
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
index 5fc760156f..bf4f00059e 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt
@@ -1,7 +1,7 @@
package com.tangem.tap.di.domain
import com.tangem.domain.card.repository.CardSdkConfigRepository
-import com.tangem.domain.demo.IsDemoCardUseCase
+import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
@@ -22,20 +22,24 @@ internal object TransactionDomainModule {
@Provides
@ViewModelScoped
fun provideGetFeeUseCase(walletManagersFacade: WalletManagersFacade): GetFeeUseCase {
- return GetFeeUseCase(walletManagersFacade)
+ return GetFeeUseCase(
+ walletManagersFacade = walletManagersFacade,
+ demoConfig = DemoConfig(),
+ )
}
@Provides
@ViewModelScoped
fun provideSendTransactionUseCase(
- isDemoCardUseCase: IsDemoCardUseCase,
cardSdkConfigRepository: CardSdkConfigRepository,
transactionRepository: TransactionRepository,
+ walletManagersFacade: WalletManagersFacade,
): SendTransactionUseCase {
return SendTransactionUseCase(
- isDemoCardUseCase = isDemoCardUseCase,
+ demoConfig = DemoConfig(),
cardSdkConfigRepository = cardSdkConfigRepository,
transactionRepository = transactionRepository,
+ walletManagersFacade = walletManagersFacade,
)
}
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
index 2cb96aa486..cfdcf74a19 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
@@ -7,7 +7,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.extensions.Result
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.redux.AppState
-import com.tangem.tap.features.demo.DemoTransactionSender
+import com.tangem.domain.demo.DemoTransactionSender
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.send.redux.AmountActionUi
import com.tangem.tap.features.send.redux.FeeAction
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
index 2922bd5b22..519637bd2f 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
@@ -31,7 +31,7 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TangemSigner
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
-import com.tangem.tap.features.demo.DemoTransactionSender
+import com.tangem.domain.demo.DemoTransactionSender
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
index 0402c5e2de..23484d916d 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
@@ -13,8 +13,8 @@ import androidx.core.view.postDelayed
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import arrow.core.getOrElse
@@ -54,14 +54,13 @@ import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
-import com.tangem.tap.mainScope
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentSendBinding
import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
@@ -73,6 +72,7 @@ private const val EDIT_TEXT_INPUT_DEBOUNCE = 400L
/**
[REDACTED_AUTHOR]
*/
+@Suppress("LargeClass")
@OptIn(FlowPreview::class)
@AndroidEntryPoint
class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
@@ -103,18 +103,25 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
lifecycle.addObserver(viewModel)
sendSubscriber.initViewModel(viewModel)
Analytics.send(Token.Send.ScreenOpened())
- listenToQrCode()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ subscribeToQrCodeScanner()
+ subscribeToTransactionExtrasFields()
+ subscribeToAddressField()
+ subscribeToAmountField()
+ }
+ }
+
addBackPressHandler(this)
etAmountToSend = view.findViewById(R.id.etAmountToSend)
initSendButtonStates()
setupAddressLayout()
- setupTransactionExtrasLayout()
setupAmountLayout()
setupFeeLayout()
setupWarningMessages()
@@ -149,14 +156,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
setOnFocusChangeListener { _, hasFocus ->
store.dispatch(TruncateOrRestore(!hasFocus))
}
-
- inputtedTextAsFlow()
- .debounce(EDIT_TEXT_INPUT_DEBOUNCE)
- .filter { store.state.sendState.addressState.viewFieldValue.value != it }
- .onEach {
- store.dispatch(AddressActionUi.HandleUserInput(it))
- }
- .launchIn(mainScope)
}
imvPaste.setOnClickListener {
@@ -183,31 +182,41 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
}
- private fun listenToQrCode() {
- lifecycleScope.launch {
- listenToQrScanningUseCase(SourceType.SEND)
- .getOrElse { emptyFlow() }
- .flowWithLifecycle(this@SendFragment.lifecycle, minActiveState = Lifecycle.State.CREATED)
- .collect { rawQr ->
- delay(200)
-
- // Delayed launch is needed in order for the UI to be drawn and to process the sent events.
- // If do not use the delay, then etAmount error field is not displayed when
- // inserting an incorrect amount by shareUri
- cryptoCurrency?.let { cryptoCurrency ->
- parseQrCodeUseCase(rawQr, cryptoCurrency = cryptoCurrency).fold(
- ifLeft = {
- onCodeScanned(QrResult(address = rawQr))
- Timber.w(it)
- },
- ifRight = { onCodeScanned(it) },
- )
- } ?: onCodeScanned(QrResult(address = rawQr))
- }
- }
+ private fun CoroutineScope.subscribeToAddressField() = with(binding.lSendAddress) {
+ etAddress.inputtedTextAsFlow()
+ .debounce(EDIT_TEXT_INPUT_DEBOUNCE)
+ .filter { store.state.sendState.addressState.viewFieldValue.value != it }
+ .onEach {
+ store.dispatch(AddressActionUi.HandleUserInput(it))
+ }
+ .launchIn(this@subscribeToAddressField)
}
- private fun setupTransactionExtrasLayout() = with(binding.lSendAddress) {
+ private fun CoroutineScope.subscribeToAmountField() {
+ etAmountToSend.inputtedTextAsFlow()
+ .debounce(EDIT_TEXT_INPUT_DEBOUNCE)
+ .filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() }
+ .onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) }
+ .launchIn(this)
+ }
+
+ private fun CoroutineScope.subscribeToQrCodeScanner() {
+ listenToQrScanningUseCase(SourceType.SEND)
+ .getOrElse { emptyFlow() }
+ .onEach { rawQr ->
+ cryptoCurrency?.let { cryptoCurrency ->
+ parseQrCodeUseCase(rawQr, cryptoCurrency = cryptoCurrency).fold(
+ ifLeft = {
+ onCodeScanned(QrResult(address = rawQr))
+ Timber.w(it)
+ },
+ ifRight = { onCodeScanned(it) },
+ )
+ } ?: onCodeScanned(QrResult(address = rawQr))
+ }.launchIn(this)
+ }
+
+ private fun CoroutineScope.subscribeToTransactionExtrasFields() = with(binding.lSendAddress) {
// TODO: [REDACTED_TASK_KEY]
etXlmMemo.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@@ -216,7 +225,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.xlmMemo?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.XlmMemo.HandleUserInput(it)) }
- .launchIn(mainScope)
+ .launchIn(this@subscribeToTransactionExtrasFields)
etDestinationTag.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@@ -225,7 +234,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.xrpDestinationTag?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.XrpDestinationTag.HandleUserInput(it)) }
- .launchIn(mainScope)
+ .launchIn(this@subscribeToTransactionExtrasFields)
etBinanceMemo.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@@ -234,7 +243,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.binanceMemo?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.BinanceMemo.HandleUserInput(it)) }
- .launchIn(mainScope)
+ .launchIn(this@subscribeToTransactionExtrasFields)
etTonMemo.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@@ -243,7 +252,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.tonMemoState?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.TonMemo.HandleUserInput(it)) }
- .launchIn(mainScope)
+ .launchIn(this@subscribeToTransactionExtrasFields)
etCosmosMemo.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@@ -252,7 +261,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.cosmosMemoState?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.CosmosMemo.HandleUserInput(it)) }
- .launchIn(mainScope)
+ .launchIn(this@subscribeToTransactionExtrasFields)
etHederaMemo.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@@ -261,7 +270,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.hederaMemoState?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.HederaMemo.HandleUserInput(it)) }
- .launchIn(mainScope)
+ .launchIn(this@subscribeToTransactionExtrasFields)
etAlgorandMemo.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@@ -270,7 +279,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
info.algorandMemoState?.viewFieldValue?.value != it
}
.onEach { store.dispatch(TransactionExtrasAction.AlgorandMemo.HandleUserInput(it)) }
- .launchIn(mainScope)
+ .launchIn(this@subscribeToTransactionExtrasFields)
}
private fun onCodeScanned(parsedQr: QrResult) {
@@ -343,12 +352,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
if (!hasFocus && etAmountToSend.text?.toString() == "") etAmountToSend.setText("0")
}
- etAmountToSend.inputtedTextAsFlow()
- .debounce(EDIT_TEXT_INPUT_DEBOUNCE)
- .filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() }
- .onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) }
- .launchIn(mainScope)
-
etAmountToSend.setOnImeActionListener(EditorInfo.IME_ACTION_DONE) {
it.hideSoftKeyboard()
it.clearFocus()
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
index 228273c9bd..c004c38b21 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
@@ -135,7 +135,7 @@ class MoonPayService(
.appendQueryParameter("apiKey", apiKey)
.appendQueryParameter("baseCurrencyCode", moonpayCurrency.currencyCode.uppercase())
.appendQueryParameter("refundWalletAddress", walletAddress)
- .appendQueryParameter("redirectURL", "tangem://redirect_sell")
+ .appendQueryParameter("redirectURL", "tangem://redirect_sell?currency_id=${cryptoCurrency.id.value}")
if (isDarkTheme) uri.appendQueryParameter("theme", "dark")
diff --git a/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt
index 579c72689a..db28975f63 100644
--- a/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt
+++ b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt
@@ -11,6 +11,7 @@ class SellCurrencyDeepLink(val onReceive: (data: Data) -> Unit) : DeepLink {
transactionId = params["transactionId"] ?: return,
baseCurrencyAmount = params["baseCurrencyAmount"] ?: return,
depositWalletAddress = params["depositWalletAddress"] ?: return,
+ currencyId = params["currency_id"] ?: return,
depositWalletAddressTag = params["depositWalletAddressTag"],
)
@@ -21,6 +22,7 @@ class SellCurrencyDeepLink(val onReceive: (data: Data) -> Unit) : DeepLink {
val transactionId: String,
val baseCurrencyAmount: String,
val depositWalletAddress: String,
+ val currencyId: String,
val depositWalletAddressTag: String?,
)
}
\ No newline at end of file
diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
index d5cd06e891..b3f4ebb779 100644
--- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
+++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
@@ -9,7 +9,7 @@
},
{
"name": "REDESIGNED_SEND_SCREEN_ENABLED",
- "version": "5.9.0"
+ "version": "5.9.1"
},
{
"name": "LOCAL_USER_LOGS_ENABLED",
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index 1e87046df0..da22a118ee 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -4,12 +4,16 @@
Валюты
Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств.
Обратиться в поддержку
+ Попробовать снова
Эта функция недоступна в демонстрационном режиме
Причина: %s
Не могу отправить транзакцию
Выбранный кошелёк не поддерживает сеть %1$s
Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен.
Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки.
+ Спасибо за ваш отзыв. Мы ответим в кратчайшие сроки.
+ Ваши предложения отправлены
+ Пожалуйста, попробуйте приложить карту в точности, как показано на анимации, или запросите поддержку.
У вас возникли трудности со сканированием карты?
Эта карта не предназначена для работы с этим приложением
Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться.
@@ -24,6 +28,8 @@
Тёмная
Светлая
Как в системе
+ При выборе настройки как в системе приложение будет использовать тему в соответствии с настройками вашего устройства
+ Системная
Тема
Настройки приложения
Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\"
@@ -51,6 +57,10 @@
Заводские настройки
Тип безопасности
Настройки карты
+ Ввиду особенностей сети Cardano при транзакции токена %1$s помимо комиссии сети будет списано %2$s
+ Для совершения транзакции %1$s, вам необходимо внести немного %2$s (%3$s), чтобы покрыть комиссию сети и минимальное значение ADA для отправки.
+ Недостаточно ADA для отправки токена
+ Вывод всего баланса ADA невозможен при наличии средств на токенах сети Cardano. Сначала выведите средства на ваших токенах.
Принять
Доступ запрещен
Применить
@@ -71,6 +81,7 @@
Создать
Удалить
Отключено
+ Отключить
Готово
Включить
Включено
@@ -96,12 +107,14 @@
Нет адреса
OK
Основная карта
- Кодовая фраза
+ Парольная фраза
+ Вставить
Подробнее
Получить
Отклонить
Перезагрузить
Переименовать
+ Повторить
Сохранить изменения
Искать
Поиск токенов
@@ -125,6 +138,7 @@
Я понял
Произошла ошибка. Пожалуйста, попробуйте снова.
Недоступно
+ Предупреждение
Да
Адрес контракта скопирован!
Доступные сети
@@ -171,6 +185,7 @@
Скрывать балансы жестом переворота
Эмитент
Подписано
+ Если вы забудете код, то потеряете доступ к своим средствам. Восстановление кода невозможно.
Подробности
Проверьте подключение с интернетом или переключитесь на другую сеть
Условия использования
@@ -183,9 +198,9 @@
Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами
Выберите провайдера
Произошла ошибка. Код: %s
- К сожалению обмен указанной пары через выбранного провайдера на данный момент не возможен. Попробуйте совершить обмен позже. (Код: %s)
+ К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s)
Выбранный провайдер не доступен для обмена. Попробуйте позже. (Код: %s)
- В данный момент обмен не возможен. Попробуйте позже. (Код: %s)
+ В данный момент обмен невозможен. Попробуйте позже. (Код: %s)
Курс обмена
Обмен через %s
Чтобы вернуть ваши деньги, посетите сайт провайдера
@@ -288,7 +303,7 @@
Ошибка активации
Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить?
Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас.
- Кодовая фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов.
+ Парольная фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов.
Добавить резервную карту
Сканировать карту #%d
Создать резервную копию
@@ -425,8 +440,6 @@
Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку.
Приготовьте свою карту
Уже содержится в введенном адресе
- Вычесть
- Недостаточно средств для покрытия комиссии сети. Вычесть недостающую сумму для покрытия комиссии из отправляемой суммы?
Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна.
Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить?
Причина: %1$s\nКод: %2$s
@@ -478,7 +491,7 @@
Возможны задержки по транзакции
Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму.
Лимит транзакции
- Необязательное
+ Опционально
Пожалуйста, совместите свой QR-код с квадратом, чтобы отсканировать его. Убедитесь, что вы сканируете адрес в сети %s.
Последние
Получатель
@@ -543,6 +556,7 @@
Выбранная операция в данный момент недоступна. Попробуйте позже.
Обмен %s не доступен. Но мы работаем над его добавлением.
В данный момент продажа монеты %s недоступна. Но мы работаем над её добавлением.
+ Выберите адрес
Сгенерировать XPUB
Скрыть
Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами.
@@ -652,6 +666,10 @@
Пожалуйста, измените сумму для обмена
Возможно, данная карта - образец или подделка
Ошибка проверки подлинности
+ Ассоциировать
+ Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%.4f %s
+ Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять
+ Ассоциируете свой токен
На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства.
Малое количество подписей
Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети.
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index f0b93b187c..efe98087bb 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -4,12 +4,16 @@
Manage tokens
Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds.
Request support
+ Try again
This feature is disabled in Demo mode
Reason: %s
Can\'t send a transaction
The selected does not support the %1$s network
To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset.
Tokens in %1$s network are not supported by this card due to firmware limitation.
+ Thank you for your feedback. We will respond as soon as possible
+ Your suggestions were sent
+ Please try to tap the card exactly as shown in the animation or request support.
Are you having difficulty scanning your card?
This card is not designed to work with this app
Default Fee
@@ -25,6 +29,8 @@
Dark
Light
System default
+ If system is selected, the app will auto-adjust based on your device\'s system settings
+ System
Theme
App Settings
To hide or show your balances, simply flip your device screen down, or switch it off in Settings
@@ -50,6 +56,10 @@
Reset to Factory Settings
Security Mode
Card Settings
+ Due to the peculiarities of the Cardano network, when transacting the %1$s token, in addition to the network commission, %2$s will be charged
+ To make a %1$s transaction, you must deposit some %2$s (%3$s) to cover the network fee and minimum ADA value
+ Insufficient ADA to token transfer
+ Withdrawal of the entire ADA balance is not possible if funds are available in Cardano network tokens. First, withdraw funds on your tokens.
Accept
Access denied
Apply
@@ -70,6 +80,7 @@
Create
Delete
Disabled
+ Disconnect
Done
Enable
Enabled
@@ -96,11 +107,13 @@
OK
Primary Card
Passphrase
+ Paste
Read more
Receive
Reject
Reload
Rename
+ Retry
Save changes
Search
Search tokens
@@ -124,6 +137,7 @@
I understand
There was an error. Please try again.
Unreachable
+ Warning
Yes
Contract address copied!
Available networks
@@ -170,6 +184,7 @@
Flip-to-Hide Balances
Issuer
Signed
+ If you forget the code you will lose access to your funds. Code recovery is not possible.
Details
Check your internet connection or switch to a different network
Terms of Service
@@ -420,13 +435,13 @@
Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet.
Get your card ready!
Already included in the entered address
- Subtract
- Not enough funds to cover the network fee. Do you want to subtract the amount required to cover the fee?
The commission amount is %s times the recommended amount. Make sure that the custom settings are correct.
You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue?
Reason: %1$s\nCode: %2$s
The transaction is not completed
Amount
+ Base fee
+ Represents the part of the transaction fee that goes to the miner
%1$s, %2$s
Address
Destination Tag
@@ -474,6 +489,8 @@
Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount.
Transaction limitation
Optional
+ Priority fee
+ Represents the minimum gasUsed multiplier required for a transaction to be included in a block. This is the part of the transaction fee that is burnt.
Please align your QR code with the square to scan it. Ensure you scan %s network address.
Recent
Recipient
@@ -540,6 +557,7 @@
This operation is currently unavailable. Please try again later.
%s swap is not available. But we are working on adding it.
Sell of the %s coin is currently unavailable. But we are working on adding it.
+ Choose address
Generate XPUB
Hide
You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.
@@ -649,6 +667,10 @@
Please change the amount to swap
This card might be a production sample or counterfeit
Authenticity check failed
+ Associate
+ This token must be associated with your Hedera account before you can receive it. Association fee ~%.4f %s
+ This token must be associated with your Hedera account before you can receive it
+ Associate your token
Only %s signatures are left on this card. You must withdraw all of your funds.
Low signature count
Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds.
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt
index 712fc8cb52..f3cbfca76d 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt
@@ -47,14 +47,15 @@ inline fun SegmentedButtons(
selectedColor: Color = TangemTheme.colors.background.action,
dividerColor: Color = TangemTheme.colors.stroke.primary,
showIndication: Boolean = true,
- selectedItem: T? = null,
+ initialSelectedItem: T? = null,
+ isEnabled: Boolean = true,
crossinline buttonContent: @Composable (T) -> Unit,
) {
if (config.isEmpty() || config.size == 1) return
- var selected by remember {
- val selectedIndex = if (selectedItem == null) 0 else config.indexOf(selectedItem)
- mutableIntStateOf(selectedIndex)
+ var selectedIndex by remember {
+ val index = if (initialSelectedItem == null) 0 else config.indexOf(initialSelectedItem)
+ mutableIntStateOf(index)
}
Row(
@@ -69,7 +70,7 @@ inline fun SegmentedButtons(
val rightRadius = if (index == config.lastIndex) TangemTheme.dimens.radius26 else TangemTheme.dimens.radius0
val animateColor by animateColorAsState(
- targetValue = if (index == selected) selectedColor else color,
+ targetValue = if (index == selectedIndex) selectedColor else color,
label = "Segmented Button Selected Color Animation",
animationSpec = spring(stiffness = Spring.StiffnessMedium),
)
@@ -86,11 +87,12 @@ inline fun SegmentedButtons(
),
)
.clickable(
+ enabled = isEnabled,
interactionSource = remember { MutableInteractionSource() },
indication = if (showIndication) LocalIndication.current else null,
) {
+ selectedIndex = index
onClick(config[index])
- selected = index
},
) {
buttonContent.invoke(config[index])
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt
index 3255b50af7..76d5c9d094 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt
@@ -4,11 +4,16 @@ import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.currency.DefaultCurrencyIcon
+private const val GRAY_SCALE_SATURATION = 0f
+private const val GRAY_SCALE_ALPHA = 0.4f
+
/**
* Simple icon from network
*
@@ -20,16 +25,19 @@ import com.tangem.core.ui.components.currency.DefaultCurrencyIcon
fun FiatIcon(
url: String?,
size: Dp,
+ isGrayscale: Boolean,
modifier: Modifier = Modifier,
@DrawableRes fallbackResId: Int = R.drawable.ic_shape_circle,
) {
val iconData: Any = if (url.isNullOrBlank()) fallbackResId else url
+ val alpha = if (isGrayscale) GRAY_SCALE_ALPHA else 1f
+ val colorFilter = if (isGrayscale) GrayscaleColorFilter else null
DefaultCurrencyIcon(
iconData = iconData,
size = size,
- alpha = 1f,
- colorFilter = null,
+ alpha = alpha,
+ colorFilter = colorFilter,
errorIcon = {
Image(
painter = painterResource(id = fallbackResId),
@@ -38,4 +46,7 @@ fun FiatIcon(
},
modifier = modifier,
)
-}
\ No newline at end of file
+}
+
+private val GrayscaleColorFilter: ColorFilter
+ get() = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(GRAY_SCALE_SATURATION) })
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt
index 19703e82b9..fbcc6e3caa 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/converter/CryptoCurrencyToIconStateConverter.kt
@@ -20,6 +20,26 @@ class CryptoCurrencyToIconStateConverter : Converter getIconStateForCoin(
+ coin = currency,
+ isUnreachable = value.value.isError,
+ forceGrayscale = forceGrayscale,
+ )
+ is CryptoCurrency.Token -> getIconStateForToken(
+ token = currency,
+ isErrorStatus = value.value.isError,
+ forceGrayscale = forceGrayscale,
+ showCustomBadge = showCustomTokenBadge,
+ )
+ }
+ }
+
fun convert(currency: CryptoCurrency): TokenIconState {
return when (currency) {
is CryptoCurrency.Coin -> getIconStateForCoin(currency, isUnreachable = false)
@@ -27,18 +47,27 @@ class CryptoCurrencyToIconStateConverter : Converter Unit, modifi
modifier = modifier,
) {
Text(
- text = "Paste",
+ text = stringResource(R.string.common_paste),
style = TangemTheme.typography.button,
color = TangemTheme.colors.text.primary2,
modifier = Modifier
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt
index 2adcbdc369..6f23701fea 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt
@@ -16,7 +16,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.res.TangemTheme
-import com.tangem.core.ui.utils.toDateFormat
+import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.core.ui.utils.toTimeFormat
/**
@@ -47,7 +47,11 @@ fun TransactionDoneTitle(@StringRes titleRes: Int, date: Long, modifier: Modifie
.padding(top = TangemTheme.dimens.spacing16),
)
Text(
- text = stringResource(id = R.string.send_date_format, date.toDateFormat(), date.toTimeFormat()),
+ text = stringResource(
+ id = R.string.send_date_format,
+ date.toTimeFormat(DateTimeFormatters.dateFormatter),
+ date.toTimeFormat(),
+ ),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt
index 42309223b1..b724559114 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt
@@ -40,6 +40,29 @@ object BigDecimalFormatter {
}
}
+ fun formatCryptoAmountUncapped(
+ cryptoAmount: BigDecimal?,
+ cryptoCurrency: CryptoCurrency,
+ locale: Locale = Locale.getDefault(),
+ ): String {
+ if (cryptoAmount == null) return EMPTY_BALANCE_SIGN
+
+ val formatter = NumberFormat.getNumberInstance(locale).apply {
+ maximumFractionDigits = cryptoCurrency.decimals
+ minimumFractionDigits = 2
+ isGroupingUsed = true
+ roundingMode = RoundingMode.DOWN
+ }
+
+ return formatter.format(cryptoAmount).let {
+ if (cryptoCurrency.symbol.isEmpty()) {
+ it
+ } else {
+ it + "\u2009${cryptoCurrency.symbol}"
+ }
+ }
+ }
+
fun formatCryptoAmount(
cryptoAmount: BigDecimal?,
cryptoCurrency: CryptoCurrency,
diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt
index 8d596fc41f..bc2560f6e9 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt
@@ -11,7 +11,7 @@ import org.joda.time.format.DateTimeFormatter
* If [this] timestamp is today or yesterday, returns relative date,
* otherwise returns formatting date.
*/
-fun Long.toDateFormat(formatter: DateTimeFormatter = DateTimeFormatters.dateFormatter): String {
+fun Long.toDateFormatWithTodayYesterday(formatter: DateTimeFormatter = DateTimeFormatters.dateFormatter): String {
val localDate = DateTime(this, DateTimeZone.getDefault())
return if (localDate.isToday() || localDate.isYesterday()) {
DateUtils.getRelativeTimeSpanString(
diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt
index cce12822c5..c4677126a6 100644
--- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt
+++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt
@@ -6,9 +6,9 @@ import com.tangem.domain.qrscanning.models.QrResult
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.repository.QrScanningEventsRepository
import com.tangem.domain.tokens.model.CryptoCurrency
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.yield
import java.math.BigDecimal
import java.net.URLDecoder
@@ -16,15 +16,20 @@ internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository {
private data class QrScanningEvent(val type: SourceType, val qrCode: String)
- private val scannedEvents = MutableSharedFlow()
+ private val scannedEvents = MutableSharedFlow(replay = 1)
override suspend fun emitResult(type: SourceType, qrCode: String) {
scannedEvents.emit(QrScanningEvent(type, qrCode))
}
+ @OptIn(ExperimentalCoroutinesApi::class)
override fun subscribeToScanningResults(type: SourceType) = scannedEvents
.filter { it.type == type }
.map { it.qrCode }
+ .onEach {
+ yield() // if we have more than one sub, we must allow them to collect emitted value
+ scannedEvents.resetReplayCache()
+ }
override fun parseQrCode(qrCode: String, cryptoCurrency: CryptoCurrency): QrResult {
val withoutSchema = stripSchema(qrCode, cryptoCurrency)
diff --git a/domain/demo/build.gradle.kts b/domain/demo/build.gradle.kts
index f0c56e2a73..a9e6de5249 100644
--- a/domain/demo/build.gradle.kts
+++ b/domain/demo/build.gradle.kts
@@ -10,4 +10,5 @@ android {
dependencies {
implementation(deps.tangem.blockchain)
+ implementation(deps.tangem.card.core)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoTransactionSender.kt b/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt
similarity index 98%
rename from app/src/main/java/com/tangem/tap/features/demo/DemoTransactionSender.kt
rename to domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt
index faecf5aac1..e98373f775 100644
--- a/app/src/main/java/com/tangem/tap/features/demo/DemoTransactionSender.kt
+++ b/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt
@@ -1,4 +1,4 @@
-package com.tangem.tap.features.demo
+package com.tangem.domain.demo
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.transaction.Fee
diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt
index 87015b1305..db909d08f3 100644
--- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt
+++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt
@@ -184,8 +184,9 @@ sealed class CryptoCurrency : Parcelable {
}
private companion object {
+ // should use delimiters that could be used in URL not like path or query delimiters
const val PREFIX_DELIMITER = '_'
- const val SUFFIX_DELIMITER = '#'
+ const val SUFFIX_DELIMITER = ';'
const val DERIVATION_PATH_DELIMITER = 'd'
}
}
diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt
index 22b7991290..3f5f7485d4 100644
--- a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt
+++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt
@@ -4,7 +4,7 @@ import com.tangem.core.ui.extensions.TextReference
sealed class SendTransactionError {
- object DemoCardError : SendTransactionError()
+ data object DemoCardError : SendTransactionError()
data class DataError(val message: String?) : SendTransactionError()
@@ -12,7 +12,7 @@ sealed class SendTransactionError {
data class BlockchainSdkError(val code: Int, val message: String?) : SendTransactionError()
- object UserCancelledError : SendTransactionError()
+ data object UserCancelledError : SendTransactionError()
data class CreateAccountUnderfunded(val amount: String) : SendTransactionError()
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
index ae697ceb08..2f113a02a0 100644
--- 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
@@ -6,10 +6,12 @@ import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.extensions.Result
+import com.tangem.domain.demo.DemoConfig
+import com.tangem.domain.demo.DemoTransactionSender
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.domain.wallets.models.UserWallet
import java.math.BigDecimal
/**
@@ -17,23 +19,31 @@ import java.math.BigDecimal
*/
class GetFeeUseCase(
private val walletManagersFacade: WalletManagersFacade,
+ private val demoConfig: DemoConfig,
) {
suspend operator fun invoke(
amount: BigDecimal,
destination: String,
- userWalletId: UserWalletId,
+ userWallet: UserWallet,
cryptoCurrency: CryptoCurrency,
) = either {
catch(
block = {
- val result = requireNotNull(
- walletManagersFacade.getFee(
- amount = convertCryptoCurrencyToAmount(cryptoCurrency, amount),
+ val amountData = convertCryptoCurrencyToAmount(cryptoCurrency, amount)
+
+ val result = if (demoConfig.isDemoCardId(userWallet.scanResponse.card.cardId)) {
+ demoTransactionSender(userWallet, cryptoCurrency).getFee(
+ amount = amountData,
destination = destination,
- userWalletId = userWalletId,
+ )
+ } else {
+ walletManagersFacade.getFee(
+ amount = amountData,
+ destination = destination,
+ userWalletId = userWallet.walletId,
network = cryptoCurrency.network,
- ),
- ) { "Fee is null" }
+ ) ?: error("Fee is null")
+ }
val maybeFee = when (result) {
is Result.Success -> result.data
@@ -47,6 +57,17 @@ class GetFeeUseCase(
)
}
+ private suspend fun demoTransactionSender(
+ userWallet: UserWallet,
+ cryptoCurrency: CryptoCurrency,
+ ): DemoTransactionSender {
+ return DemoTransactionSender(
+ walletManagersFacade
+ .getOrCreateWalletManager(userWallet.walletId, cryptoCurrency.network)
+ ?: error("WalletManager is null"),
+ )
+ }
+
private fun convertCryptoCurrencyToAmount(cryptoCurrency: CryptoCurrency, amount: BigDecimal) = Amount(
currencySymbol = cryptoCurrency.symbol,
value = amount,
diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt
index d2c9cbd085..379d90d4be 100644
--- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt
+++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt
@@ -5,6 +5,7 @@ import arrow.core.left
import arrow.core.right
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.TransactionData
+import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.network.ResultChecker
import com.tangem.common.core.TangemSdkError
@@ -12,20 +13,23 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
-import com.tangem.domain.demo.IsDemoCardUseCase
+import com.tangem.domain.demo.DemoConfig
+import com.tangem.domain.demo.DemoTransactionSender
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.transaction.R
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.domain.transaction.error.SendTransactionError.Companion.USER_CANCELLED_ERROR_CODE
+import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.sdk.extensions.localizedDescriptionRes
import com.tangem.utils.toFormattedString
class SendTransactionUseCase(
- private val isDemoCardUseCase: IsDemoCardUseCase,
+ private val demoConfig: DemoConfig,
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val transactionRepository: TransactionRepository,
+ private val walletManagersFacade: WalletManagersFacade,
) {
suspend operator fun invoke(
txData: TransactionData,
@@ -39,8 +43,13 @@ class SendTransactionUseCase(
cardSdkConfigRepository.setLinkedTerminal(false)
}
val sendResult = try {
- if (isDemoCardUseCase(cardId = userWallet.cardId)) {
- SendTransactionError.DemoCardError.left()
+ if (demoConfig.isDemoCardId(cardId = userWallet.cardId)) {
+ sendDemo(
+ userWallet = userWallet,
+ network = network,
+ transactionData = txData,
+ signer = signer,
+ )
} else {
transactionRepository.sendTransaction(
txData = txData,
@@ -66,6 +75,27 @@ class SendTransactionUseCase(
)
}
+ private suspend fun sendDemo(
+ userWallet: UserWallet,
+ network: Network,
+ transactionData: TransactionData,
+ signer: TransactionSigner,
+ ): Either {
+ val demoTransactionSender = DemoTransactionSender(
+ walletManagersFacade
+ .getOrCreateWalletManager(userWallet.walletId, network)
+ ?: error("WalletManager is null"),
+ )
+
+ val result = demoTransactionSender.send(transactionData = transactionData, signer = signer)
+
+ return if (result is SimpleResult.Failure && result.error.customMessage.contains(DemoTransactionSender.ID)) {
+ SendTransactionError.DemoCardError.left()
+ } else {
+ result.right()
+ }
+ }
+
private fun handleError(result: SimpleResult.Failure): SendTransactionError {
if (ResultChecker.isNetworkError(result)) {
return SendTransactionError.NetworkError(
diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt
index 94f072a0ac..609e2df784 100644
--- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt
+++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt
@@ -93,7 +93,7 @@ private fun SegmentSeedBlock(state: SegmentSeedState, modifier: Modifier = Modif
SegmentedButtons(
modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing76),
config = state.seedSegments,
- selectedItem = state.selectedSeedType,
+ initialSelectedItem = state.selectedSeedType,
onClick = {
state.onSelectType(it)
},
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt
index 577380dc6b..f232473da8 100644
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt
@@ -4,6 +4,7 @@ import android.Manifest
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
+import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -15,6 +16,8 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.google.mlkit.vision.common.InputImage
import com.tangem.core.ui.res.TangemTheme
@@ -25,6 +28,8 @@ import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter
import com.tangem.feature.qrscanning.presentation.QrScanningContent
import com.tangem.feature.qrscanning.viewmodel.QrScanningViewModel
import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import javax.inject.Inject
@@ -69,11 +74,24 @@ internal class QrScanningFragment : ComposeFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- viewModel.setRouter(innerRouter, galleryLauncher)
+ viewModel.setRouter(innerRouter)
cameraExecutor = Executors.newSingleThreadExecutor()
requestCameraPermission()
}
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.launchGalleryEvent
+ .collect {
+ galleryLauncher.launch(it.imageFilter)
+ delay(timeMillis = 2000)
+ }
+ }
+ }
+ }
+
override fun onResume() {
super.onResume()
checkPermissionGranted()
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt
index a9c37e465c..2ff968422a 100644
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt
@@ -1,6 +1,5 @@
package com.tangem.feature.qrscanning.viewmodel
-import androidx.activity.result.ActivityResultLauncher
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter
import kotlinx.coroutines.CoroutineScope
@@ -11,23 +10,14 @@ internal open class BaseQrScanningClickIntents {
protected val router: QrScanningInnerRouter get() = _router
protected val viewModelScope: CoroutineScope get() = _viewModelScope
protected val source: SourceType get() = _source
- protected val galleryLauncher: ActivityResultLauncher get() = _galleryLauncher
private var _router: QrScanningInnerRouter by Delegates.notNull()
private var _viewModelScope: CoroutineScope by Delegates.notNull()
private var _source: SourceType by Delegates.notNull()
- private var _galleryLauncher: ActivityResultLauncher by Delegates.notNull()
-
- open fun initialize(
- router: QrScanningInnerRouter,
- source: SourceType,
- galleryLauncher: ActivityResultLauncher,
- coroutineScope: CoroutineScope,
- ) {
+ open fun initialize(router: QrScanningInnerRouter, source: SourceType, coroutineScope: CoroutineScope) {
_router = router
_viewModelScope = coroutineScope
_source = source
- _galleryLauncher = galleryLauncher
}
}
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt
index 64d837b7f4..075894e1c6 100644
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt
@@ -5,11 +5,16 @@ import com.tangem.feature.qrscanning.presentation.QrScanningStateController
import com.tangem.feature.qrscanning.presentation.transformers.DismissBottomSheetTransformer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.scopes.ViewModelScoped
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
interface QrScanningClickIntents {
+ val launchGallery: SharedFlow
+
fun onBackClick()
fun onQrScanned(qrCode: String)
@@ -17,6 +22,11 @@ interface QrScanningClickIntents {
fun onGalleryClicked()
}
+@JvmInline
+value class GalleryRequest(
+ val imageFilter: String,
+)
+
@ViewModelScoped
internal class QrScanningClickIntentsImplementor @Inject constructor(
private val stateHolder: QrScanningStateController,
@@ -26,6 +36,11 @@ internal class QrScanningClickIntentsImplementor @Inject constructor(
private var isScanned = false
+ override val launchGallery = MutableSharedFlow(
+ extraBufferCapacity = 1,
+ onBufferOverflow = BufferOverflow.DROP_LATEST,
+ )
+
override fun onBackClick() = router.popBackStack()
override fun onQrScanned(qrCode: String) {
@@ -41,7 +56,7 @@ internal class QrScanningClickIntentsImplementor @Inject constructor(
}
override fun onGalleryClicked() {
- galleryLauncher.launch(GALLERY_IMAGE_FILTER)
+ launchGallery.tryEmit(GalleryRequest(imageFilter = GALLERY_IMAGE_FILTER))
if (stateHolder.value.bottomSheetConfig != null) {
stateHolder.update(DismissBottomSheetTransformer())
}
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt
index 4320634f4d..cca4825f93 100644
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt
@@ -1,6 +1,5 @@
package com.tangem.feature.qrscanning.viewmodel
-import androidx.activity.result.ActivityResultLauncher
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@@ -14,6 +13,7 @@ import com.tangem.feature.qrscanning.presentation.transformers.DismissBottomShee
import com.tangem.feature.qrscanning.presentation.transformers.InitializeQrScanningStateTransformer
import com.tangem.feature.qrscanning.presentation.transformers.ShowCameraDeniedBottomSheetTransformer
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@@ -28,12 +28,12 @@ internal class QrScanningViewModel @Inject constructor(
private val network: String? = savedStateHandle[NETWORK_KEY]
val uiState: StateFlow = stateHolder.uiState
+ val launchGalleryEvent: SharedFlow = clickIntents.launchGallery
- fun setRouter(router: QrScanningInnerRouter, galleryLauncher: ActivityResultLauncher) {
+ fun setRouter(router: QrScanningInnerRouter) {
clickIntents.initialize(
router = router,
source = source,
- galleryLauncher = galleryLauncher,
coroutineScope = viewModelScope,
)
stateHolder.update(InitializeQrScanningStateTransformer(clickIntents, source, network))
diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt
index 279154964b..74f9653422 100644
--- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt
+++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt
@@ -27,7 +27,6 @@ import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.SpacerH32
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
-import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.referral.domain.models.ExpectedAward
import com.tangem.feature.referral.domain.models.ExpectedAwards
@@ -350,7 +349,7 @@ private fun BoxScope.ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) {
modifier = Modifier.fillMaxWidth(),
actionOnNewLine = true,
shape = RoundedCornerShape(size = TangemTheme.dimens.radius8),
- containerColor = TangemColorPalette.Black,
+ containerColor = TangemTheme.colors.button.primary,
contentColor = TangemTheme.colors.text.primary2,
actionColor = TangemTheme.colors.text.primary2,
)
@@ -464,7 +463,7 @@ private fun Preview_ReferralScreen_Participant_InLightTheme() {
url = "",
expectedAwards = null,
),
- errorSnackbar = null,
+ errorSnackbar = ErrorSnackbar(DemoModeException()) {},
analytics = Analytics(
onAgreementClicked = {},
onCopyClicked = {},
@@ -493,7 +492,7 @@ private fun Preview_ReferralScreen_Participant_InDarkTheme() {
url = "",
expectedAwards = null,
),
- errorSnackbar = null,
+ errorSnackbar = ErrorSnackbar(DemoModeException()) {},
analytics = Analytics(
onAgreementClicked = {},
onCopyClicked = {},
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt
index 2f48bcb034..e99471d8de 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt
@@ -103,6 +103,10 @@ internal sealed class SendAnalyticEvents(
params = mapOf(TOKEN to token),
)
+ data object NoticeFeeCoverage : SendAnalyticEvents(
+ event = "Notice - Network Fee Coverage",
+ )
+
/** If error occurs during send transactions */
data class TransactionError(val token: String) : SendAnalyticEvents(
event = "Error - Transaction Rejected",
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt
index 6de841373c..fc7ae7daf1 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt
@@ -68,16 +68,6 @@ internal sealed class SendAlertState {
override val confirmButtonText: TextReference = resourceReference(R.string.common_continue)
}
- data class FeeCoverage(
- override val onConfirmClick: (() -> Unit),
- ) : SendAlertState() {
- override val title: TextReference? = null
- override val message: TextReference =
- resourceReference(id = R.string.send_alert_fee_coverage_title)
- override val confirmButtonText: TextReference =
- resourceReference(id = R.string.send_alert_fee_coverage_subract_text)
- }
-
data class ReserveAmount(val amount: String) : SendAlertState() {
override val title: TextReference =
resourceReference(id = R.string.send_notification_invalid_reserve_amount_title, wrappedList(amount))
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt
index d99b6bd0ff..616fad69f8 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt
@@ -48,19 +48,6 @@ internal class SendEventStateFactory(
)
}
- fun getFeeCoverageAlert(onConsume: () -> Unit): SendUiState {
- return currentStateProvider().copy(
- event = triggeredEvent(
- data = SendEvent.ShowAlert(
- SendAlertState.FeeCoverage(
- onConfirmClick = clickIntents::onSubtractSelect,
- ),
- ),
- onConsume = onConsume,
- ),
- )
- }
-
fun getFeeUpdatedAlert(fee: TransactionFee, onConsume: () -> Unit, onFeeNotIncreased: () -> Unit): SendUiState {
val state = currentStateProvider()
val feeSelector = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return state
@@ -139,9 +126,7 @@ internal class SendEventStateFactory(
return state.copy(
event = triggeredEvent(
data = SendEvent.ShowAlert(
- SendAlertState.FeeUnreachableError(
- onConfirmClick = { clickIntents.feeReload(true) },
- ),
+ SendAlertState.FeeUnreachableError(onConfirmClick = clickIntents::feeReload),
),
onConsume = onConsume,
),
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt
index e59bd0e517..3334f3faaa 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt
@@ -42,11 +42,6 @@ internal sealed class SendNotification(val config: NotificationConfig) {
),
)
- data class ReserveAmountError(val amount: String) : Error(
- title = resourceReference(R.string.send_notification_invalid_reserve_amount_title, wrappedList(amount)),
- subtitle = resourceReference(R.string.send_notification_invalid_reserve_amount_text),
- )
-
data class TransactionLimitError(
val cryptoCurrency: String,
val utxoLimit: String,
@@ -153,5 +148,10 @@ internal sealed class SendNotification(val config: NotificationConfig) {
onClick = onRefresh,
),
)
+
+ data object FeeCoverageNotification : Warning(
+ title = resourceReference(R.string.send_network_fee_warning_title),
+ subtitle = resourceReference(R.string.swapping_network_fee_warning_content),
+ )
}
}
\ 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 f3f304b7ff..b31e9559be 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
@@ -14,7 +14,6 @@ import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase
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.amount.SendAmountSubtractConverter
import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter
import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter
@@ -48,12 +47,6 @@ internal class SendStateFactory(
appCurrencyProvider = appCurrencyProvider,
)
}
- private val amountSubtractConverter by lazy(LazyThreadSafetyMode.NONE) {
- SendAmountSubtractConverter(
- currentStateProvider = currentStateProvider,
- cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
- )
- }
private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) {
SendAmountStateConverter(
appCurrencyProvider = appCurrencyProvider,
@@ -117,6 +110,7 @@ internal class SendStateFactory(
recipientState = state.recipientState
?: recipientStateConverter.convert(SendRecipientStateConverter.Data(destinationAddress, memo)),
feeState = state.feeState ?: feeStateConverter.convert(Unit),
+ sendState = confirmStateConverter.convert(Unit),
isEditingDisabled = true,
cryptoCurrencyName = cryptoCurrencyStatusProvider().currency.name,
)
@@ -264,14 +258,6 @@ internal class SendStateFactory(
//endregion
//region send
- fun onSubtractSelect(isAmountSubtractAvailable: Boolean): SendUiState {
- val state = currentStateProvider()
-
- if (!isAmountSubtractAvailable) return state
-
- return amountSubtractConverter.convert(Unit)
- }
-
fun getSendingStateUpdate(isSending: Boolean): SendUiState {
val state = currentStateProvider()
return state.copy(
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 586c55c978..2b14283039 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
@@ -48,11 +48,9 @@ internal sealed class SendStates {
val walletBalance: TextReference,
val tokenIconState: TokenIconState,
val segmentedButtonConfig: PersistentList,
+ val isSegmentedButtonsEnabled: Boolean,
val amountTextField: SendTextField.AmountField,
- val notifications: ImmutableList,
val appCurrencyCode: String,
- val isFeeLoading: Boolean,
- val subtractedFee: BigDecimal?,
) : SendStates()
/** Recipient state */
@@ -89,7 +87,6 @@ internal sealed class SendStates {
override val isPrimaryButtonEnabled: Boolean = true,
val isSending: Boolean,
val isSuccess: Boolean,
- val isSubtract: Boolean,
val transactionDate: Long,
val txUrl: String,
val ignoreAmountReduce: Boolean,
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt
index 4577927cc1..6f56b6aff3 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt
@@ -1,12 +1,10 @@
package com.tangem.features.send.impl.presentation.state.amount
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
-import com.tangem.features.send.impl.presentation.state.SendNotification
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldMaxAmountConverter
import com.tangem.utils.Provider
-import kotlinx.collections.immutable.ImmutableList
/**
* Factory to produce amount state for [SendUiState]
@@ -43,18 +41,4 @@ internal class AmountStateFactory(
}
fun getOnCurrencyChangedState(isFiat: Boolean) = amountCurrencyConverter.convert(isFiat)
-
- fun getAmountNotificationState(notifications: ImmutableList): SendUiState {
- val state = currentStateProvider()
- return state.copy(
- amountState = state.amountState?.copy(notifications = notifications),
- )
- }
-
- fun getOnAmountFeeLoadingCancel(): SendUiState {
- val state = currentStateProvider()
- return state.copy(
- amountState = state.amountState?.copy(isFeeLoading = false),
- )
- }
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt
index f06a20bf48..7e3199d1ff 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt
@@ -31,6 +31,7 @@ internal class SendAmountStateConverter(
val status = cryptoCurrencyStatusProvider()
val fiat = formatFiatAmount(status.value.fiatAmount, appCurrency.code, appCurrency.symbol)
val crypto = formatCryptoAmount(status.value.amount, status.currency.symbol, status.currency.decimals)
+ val noFeeRate = status.value.fiatRate.isNullOrZero()
return SendStates.AmountState(
walletName = userWallet.name,
@@ -38,26 +39,24 @@ internal class SendAmountStateConverter(
tokenIconState = iconStateConverter.convert(status),
amountTextField = sendAmountFieldConverter.convert(value),
isPrimaryButtonEnabled = false,
- notifications = persistentListOf(),
- isFeeLoading = false,
appCurrencyCode = appCurrency.code,
- subtractedFee = null,
- segmentedButtonConfig = if (status.value.fiatRate.isNullOrZero()) {
- persistentListOf()
- } else {
- persistentListOf(
- SendAmountSegmentedButtonsConfig(
- title = stringReference(status.currency.symbol),
- iconState = iconStateConverter.convert(status),
- isFiat = false,
+ segmentedButtonConfig = persistentListOf(
+ SendAmountSegmentedButtonsConfig(
+ title = stringReference(status.currency.symbol),
+ iconState = iconStateConverter.convertCustom(
+ value = status,
+ forceGrayscale = noFeeRate,
+ showCustomTokenBadge = false,
),
- SendAmountSegmentedButtonsConfig(
- title = stringReference(appCurrency.code),
- iconUrl = appCurrency.iconSmallUrl,
- isFiat = true,
- ),
- )
- },
+ isFiat = false,
+ ),
+ SendAmountSegmentedButtonsConfig(
+ title = stringReference(appCurrency.code),
+ iconUrl = appCurrency.iconSmallUrl,
+ isFiat = true,
+ ),
+ ),
+ isSegmentedButtonsEnabled = !noFeeRate,
)
}
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt
deleted file mode 100644
index bf0f52dd73..0000000000
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.tangem.features.send.impl.presentation.state.amount
-
-import com.tangem.core.ui.utils.parseBigDecimal
-import com.tangem.domain.tokens.model.CryptoCurrencyStatus
-import com.tangem.features.send.impl.presentation.state.SendUiState
-import com.tangem.utils.Provider
-import com.tangem.utils.converter.Converter
-import java.math.BigDecimal
-
-internal class SendAmountSubtractConverter(
- private val currentStateProvider: Provider,
- private val cryptoCurrencyStatusProvider: Provider,
-) : Converter {
- override fun convert(value: Unit): SendUiState {
- val state = currentStateProvider()
- val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
- val amountState = state.amountState ?: return state
- val feeState = state.feeState ?: return state
- val feeValue = feeState.fee?.amount?.value ?: return state
- val amountTextField = amountState.amountTextField
- val amountValue = amountTextField.cryptoAmount.value ?: return state
- val fiatRate = cryptoCurrencyStatus.value.fiatRate
- val cryptoDecimals = amountTextField.cryptoAmount.decimals
- val fiatDecimals = amountTextField.fiatAmount.decimals
-
- val feeDiff = amountState.subtractedFee?.let { feeValue.minus(it) } ?: feeValue
- val decimalCryptoValue = amountValue.minus(feeDiff)
-
- if (decimalCryptoValue < BigDecimal.ZERO) return state
-
- val decimalFiatValue = decimalCryptoValue.multiply(fiatRate)
- val cryptoValue = decimalCryptoValue.parseBigDecimal(cryptoDecimals)
- val fiatValue = decimalFiatValue.parseBigDecimal(fiatDecimals)
-
- return state.copy(
- sendState = state.sendState?.copy(isSubtract = true),
- amountState = amountState.copy(
- subtractedFee = feeValue,
- amountTextField = amountTextField.copy(
- value = cryptoValue,
- fiatValue = fiatValue,
- cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue),
- fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue),
- ),
- ),
- )
- }
-}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt
index d5662a10b7..e202bf5e25 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendConfirmStateConverter.kt
@@ -13,7 +13,6 @@ internal class SendConfirmStateConverter(
isPrimaryButtonEnabled = true,
isSending = false,
isSuccess = false,
- isSubtract = false,
transactionDate = 0L,
txUrl = "",
ignoreAmountReduce = false,
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt
index e71cdf4d14..563ec2af7f 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt
@@ -18,12 +18,11 @@ import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents
import com.tangem.features.send.impl.presentation.state.*
-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.*
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.lib.crypto.BlockchainUtils.isDogecoin
+import com.tangem.lib.crypto.BlockchainUtils.isTezos
import com.tangem.utils.Provider
-import com.tangem.utils.isNullOrZero
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@@ -31,17 +30,16 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import java.math.BigDecimal
-import java.math.BigInteger
@Suppress("LongParameterList")
internal class SendNotificationFactory(
private val cryptoCurrencyStatusProvider: Provider,
private val coinCryptoCurrencyStatusProvider: Provider,
- private val feePaidCryptoCurrencyStatusProvider: Provider,
private val currentStateProvider: Provider,
private val userWalletProvider: Provider,
private val currencyChecksRepository: CurrencyChecksRepository,
private val stateRouterProvider: Provider,
+ private val isSubtractAvailableProvider: Provider,
private val clickIntents: SendClickIntents,
private val analyticsEventHandler: AnalyticsEventHandler,
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
@@ -53,24 +51,39 @@ internal class SendNotificationFactory(
val state = currentStateProvider()
val sendState = state.sendState ?: return@map persistentListOf()
val feeState = state.feeState ?: return@map persistentListOf()
- val feeAmount = feeState.fee?.amount?.value ?: BigDecimal.ZERO
+ val balance = cryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO
+ val feeValue = feeState.fee?.amount?.value ?: BigDecimal.ZERO
+ val isFeeCoverage = checkFeeCoverage(
+ isSubtractAvailable = isSubtractAvailableProvider(),
+ balance = balance,
+ amountValue = amountValue,
+ feeValue = feeValue,
+ )
+ val sendingAmount = calculateSubtractedAmount(
+ isFeeCoverage = isFeeCoverage,
+ cryptoCurrencyStatus = cryptoCurrencyStatusProvider(),
+ amountValue = amountValue,
+ feeValue = feeValue,
+ )
buildList {
// errors
- addExceedBalanceNotification(feeAmount, amountValue)
+ addFeeUnreachableNotification(feeState.feeSelectorState)
+ addExceedBalanceNotification(feeValue, sendingAmount)
addExceedsBalanceNotification(feeState.fee)
- addMinimumAmountErrorNotification(feeAmount, amountValue)
- addDustWarningNotification(feeAmount, amountValue)
- addTransactionLimitErrorNotification(feeAmount, amountValue)
+ addMinimumAmountErrorNotification(feeValue, sendingAmount)
+ addDustWarningNotification(feeValue, sendingAmount)
+ addTransactionLimitErrorNotification(feeValue, sendingAmount)
// warnings
- addExistentialWarningNotification(feeAmount, amountValue)
- addHighFeeWarningNotification(feeAmount, amountValue, sendState.ignoreAmountReduce)
+ addExistentialWarningNotification(feeValue, amountValue)
+ addFeeCoverageNotification(isFeeCoverage)
+ addHighFeeWarningNotification(amountValue, sendState.ignoreAmountReduce)
addTooHighNotification(feeState.feeSelectorState)
addTooLowNotification(feeState)
}.toImmutableList()
}
- fun dismissNotificationState(clazz: Class): SendUiState {
+ fun dismissNotificationState(clazz: Class, isIgnored: Boolean = false): SendUiState {
val state = currentStateProvider()
val sendState = state.sendState ?: return state
val notificationsToRemove = sendState.notifications.filterIsInstance(clazz)
@@ -78,50 +91,33 @@ internal class SendNotificationFactory(
updatedNotifications.removeAll(notificationsToRemove)
return state.copy(
sendState = sendState.copy(
- ignoreAmountReduce = true,
+ ignoreAmountReduce = isIgnored,
notifications = updatedNotifications.toImmutableList(),
),
)
}
+ private fun MutableList.addFeeUnreachableNotification(feeSelectorState: FeeSelectorState) {
+ if (feeSelectorState is FeeSelectorState.Error) {
+ add(SendNotification.Warning.NetworkFeeUnreachable(clickIntents::feeReload))
+ }
+ }
+
private fun MutableList.addExceedBalanceNotification(
feeAmount: BigDecimal,
receivedAmount: BigDecimal,
) {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
- val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider()
- val feePaidCryptoCurrencyStatus = feePaidCryptoCurrencyStatusProvider()
- val cryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
- val coinCryptoAmount = coinCryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
+ val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
- val showNotification = if (cryptoCurrencyStatus.currency.id == feePaidCryptoCurrencyStatus?.currency?.id) {
- receivedAmount > cryptoAmount || feeAmount > coinCryptoAmount
- } else {
- receivedAmount + feeAmount > cryptoAmount
- }
+ if (!isSubtractAvailableProvider()) return
+ val showNotification = receivedAmount + feeAmount > balance
if (showNotification) {
add(SendNotification.Error.TotalExceedsBalance)
}
}
- private fun MutableList.addMinimumAmountErrorNotification(
- feeAmount: BigDecimal,
- receivedAmount: BigDecimal,
- ) {
- val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider()
-
- val totalAmount = feeAmount + receivedAmount
- val balance = coinCryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
-
- if (isDogecoin(coinCryptoCurrencyStatus.currency.network.id.value)) {
- val minimum = BigDecimal(DOGECOIN_MINIMUM)
- if (receivedAmount < minimum || balance - totalAmount < minimum) {
- add(SendNotification.Error.MinimumAmountError(DOGECOIN_MINIMUM))
- }
- }
- }
-
// todo temporarily disabling notification
// private suspend fun MutableList.addReserveAmountErrorNotification(recipientAddress: String) {
// val userWalletId = userWalletProvider().walletId
@@ -198,7 +194,7 @@ internal class SendNotificationFactory(
if (currencyDeposit != null && currencyDeposit > diff) {
add(
SendNotification.Error.ExistentialDeposit(
- BigDecimalFormatter.formatCryptoAmount(
+ BigDecimalFormatter.formatCryptoAmountUncapped(
cryptoAmount = currencyDeposit,
cryptoCurrency = cryptoCurrency,
),
@@ -207,16 +203,22 @@ internal class SendNotificationFactory(
}
}
+ private fun MutableList.addFeeCoverageNotification(sendingAmount: Boolean) {
+ if (sendingAmount) {
+ analyticsEventHandler.send(SendAnalyticEvents.NoticeFeeCoverage)
+ add(SendNotification.Warning.FeeCoverageNotification)
+ }
+ }
+
private fun MutableList.addHighFeeWarningNotification(
- feeAmount: BigDecimal,
sendAmount: BigDecimal,
ignoreAmountReduce: Boolean,
) {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
- val isTezos = cryptoCurrencyStatus.currency.network.id.value == Blockchain.Tezos.id
+ val isTezos = isTezos(cryptoCurrencyStatus.currency.network.id.value)
val threshold = Blockchain.Tezos.minimalAmount()
- val isTotalBalance = feeAmount.plus(sendAmount) >= balance && balance > threshold
+ val isTotalBalance = sendAmount >= balance && balance > threshold
if (!ignoreAmountReduce && isTotalBalance && isTezos) {
add(
SendNotification.Warning.HighFeeError(
@@ -233,6 +235,21 @@ internal class SendNotificationFactory(
}
}
+ // todo remove in [REDACTED_TASK_KEY]
+ private fun MutableList.addMinimumAmountErrorNotification(
+ feeAmount: BigDecimal,
+ receivedAmount: BigDecimal,
+ ) {
+ val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider()
+ val minimum = BigDecimal(DOGECOIN_MINIMUM)
+
+ val isDogecoin = isDogecoin(coinCryptoCurrencyStatus.currency.network.id.value)
+ val isExceedDustLimit = checkDustLimits(feeAmount, receivedAmount, minimum)
+ if (isDogecoin && isExceedDustLimit) {
+ add(SendNotification.Error.MinimumAmountError(DOGECOIN_MINIMUM))
+ }
+ }
+
private suspend fun MutableList.addDustWarningNotification(
feeAmount: BigDecimal,
receivedAmount: BigDecimal,
@@ -241,18 +258,12 @@ internal class SendNotificationFactory(
val dustValue = currencyChecksRepository.getDustValue(
userWalletProvider().walletId,
cryptoCurrencyStatus.currency.network,
- )
- val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
- if (dustValue != null && !balance.isNullOrZero() && receivedAmount < balance) {
- val totalAmount = feeAmount + receivedAmount
- val change = balance - totalAmount
- val isChangeLowerThanDust = change < dustValue && change != BigDecimal.ZERO
- val isShowWarning = totalAmount < dustValue || isChangeLowerThanDust
- if (isShowWarning) {
- add(
- SendNotification.Error.MinimumAmountError(dustValue.toPlainString()),
- )
- }
+ ) ?: return
+
+ if (checkDustLimits(feeAmount, receivedAmount, dustValue)) {
+ add(
+ SendNotification.Error.MinimumAmountError(dustValue.toPlainString()),
+ )
}
}
@@ -274,13 +285,9 @@ internal class SendNotificationFactory(
private fun MutableList.addTooHighNotification(feeSelectorState: FeeSelectorState) {
if (feeSelectorState !is FeeSelectorState.Content) return
- val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return
- val highValue = multipleFees.priority.amount.value ?: return
- val customAmount = feeSelectorState.customValues.firstOrNull() ?: return
- val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals)
- val diff = (customValue / highValue).toBigInteger()
- if (feeSelectorState.selectedFee == FeeType.Custom && diff > FEE_MAX_DIFF) {
- add(SendNotification.Warning.TooHigh(diff.toString()))
+
+ checkIfFeeTooHigh(feeSelectorState) { diff ->
+ add(SendNotification.Warning.TooHigh(diff))
}
}
@@ -361,8 +368,17 @@ internal class SendNotificationFactory(
return Blockchain.fromNetworkId(this.currency.network.backendId) == Blockchain.Arbitrum
}
+ private fun checkDustLimits(feeAmount: BigDecimal, receivedAmount: BigDecimal, dustValue: BigDecimal): Boolean {
+ val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
+ val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
+
+ val totalAmount = feeAmount + receivedAmount
+ val change = balance - totalAmount
+ val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
+ return receivedAmount < dustValue || isChangeLowerThanDust
+ }
+
companion object {
private const val DOGECOIN_MINIMUM = "0.01"
- internal val FEE_MAX_DIFF = BigInteger("5")
}
}
\ No newline at end of file
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
index 57e567a586..5a9062da56 100644
--- 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
@@ -1,19 +1,73 @@
package com.tangem.features.send.impl.presentation.state.fee
+import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.transaction.TransactionFee
+import com.tangem.blockchainsdk.utils.minimalAmount
+import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.impl.presentation.state.SendUiState
-import com.tangem.features.send.impl.presentation.state.confirm.SendNotificationFactory
+import com.tangem.lib.crypto.BlockchainUtils
+import java.math.BigDecimal
+import java.math.RoundingMode
/**
- * Check if sending amount with fee is greater than balance
+ * Check and calculates subtracted amount
*/
-internal fun checkFeeCoverage(state: SendUiState, cryptoCurrencyStatus: CryptoCurrencyStatus): Boolean {
- val balance = cryptoCurrencyStatus.value.amount ?: return false
- val fee = state.feeState?.fee?.amount?.value ?: return false
- val amount = state.amountState?.amountTextField?.cryptoAmount?.value ?: return false
- return balance < amount + fee && balance > fee
+internal fun checkAndCalculateSubtractedAmount(
+ isAmountSubtractAvailable: Boolean,
+ cryptoCurrencyStatus: CryptoCurrencyStatus,
+ amountValue: BigDecimal,
+ feeValue: BigDecimal,
+): BigDecimal {
+ val balance = cryptoCurrencyStatus.value.amount ?: return amountValue
+ val isFeeCoverage = checkFeeCoverage(
+ isSubtractAvailable = isAmountSubtractAvailable,
+ balance = balance,
+ amountValue = amountValue,
+ feeValue = feeValue,
+ )
+ return calculateSubtractedAmount(
+ isFeeCoverage = isFeeCoverage,
+ cryptoCurrencyStatus = cryptoCurrencyStatus,
+ amountValue = amountValue,
+ feeValue = feeValue,
+ )
+}
+
+/**
+ * Checks if sending amount with fee is greater than balance
+ */
+internal fun checkFeeCoverage(
+ isSubtractAvailable: Boolean,
+ balance: BigDecimal,
+ amountValue: BigDecimal,
+ feeValue: BigDecimal,
+): Boolean {
+ if (!isSubtractAvailable) return false
+ return balance < amountValue + feeValue && balance > feeValue
+}
+
+/**
+ * Calculates subtracted amount
+ */
+internal fun calculateSubtractedAmount(
+ isFeeCoverage: Boolean,
+ cryptoCurrencyStatus: CryptoCurrencyStatus,
+ amountValue: BigDecimal,
+ feeValue: BigDecimal,
+): BigDecimal {
+ val balance = cryptoCurrencyStatus.value.amount ?: return amountValue
+ return if (isFeeCoverage) {
+ var subtractedValue = minOf(amountValue, balance.minus(feeValue))
+ if (BlockchainUtils.isTezos(cryptoCurrencyStatus.currency.network.id.value)) {
+ val threshold = Blockchain.Tezos.minimalAmount()
+ subtractedValue = -threshold
+ }
+ subtractedValue
+ } else {
+ amountValue
+ }
}
/**
@@ -32,14 +86,16 @@ internal fun checkIfFeeTooLow(state: SendUiState): Boolean {
/**
* Check if custom fee is too high
*/
-internal fun checkIfFeeTooHigh(state: SendUiState, onShow: (String) -> Unit): Boolean {
- val feeSelectorState = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false
+internal fun checkIfFeeTooHigh(feeSelectorState: FeeSelectorState.Content, onShow: (String) -> Unit): Boolean {
val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return false
val highValue = multipleFees.priority.amount.value ?: return false
val customAmount = feeSelectorState.customValues.firstOrNull() ?: return false
val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals)
- val diff = (customValue / highValue).toBigInteger()
- val isShow = feeSelectorState.selectedFee == FeeType.Custom && diff > SendNotificationFactory.FEE_MAX_DIFF
- if (isShow) onShow(diff.toString())
+ val diff = customValue / highValue
+ val isShow = feeSelectorState.selectedFee == FeeType.Custom && diff > FEE_MAX_DIFF
+ if (isShow) onShow(diff.parseBigDecimal(ZERO_DECIMALS, RoundingMode.HALF_UP))
return isShow
-}
\ No newline at end of file
+}
+
+private val FEE_MAX_DIFF = BigDecimal("5")
+private const val ZERO_DECIMALS = 0
\ 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 67fe86cf8e..cd1fe29939 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
@@ -15,6 +15,8 @@ internal sealed class FeeSelectorState {
val customValues: ImmutableList = persistentListOf(),
) : FeeSelectorState()
+ data object Loading : FeeSelectorState()
+
data object Error : FeeSelectorState()
}
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt
index ba181f423f..a74613acd3 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt
@@ -45,8 +45,12 @@ internal class FeeStateFactory(
val state = currentStateProvider()
val feeState = state.feeState ?: return state
return state.copy(
- amountState = state.amountState?.copy(isFeeLoading = true),
feeState = feeState.copy(
+ feeSelectorState = if (feeState.feeSelectorState is FeeSelectorState.Content) {
+ feeState.feeSelectorState
+ } else {
+ FeeSelectorState.Loading
+ },
notifications = persistentListOf(),
isPrimaryButtonEnabled = false,
),
@@ -73,7 +77,6 @@ internal class FeeStateFactory(
val fee = feeConverter.convert(updatedFeeSelectorState)
return state.copy(
- amountState = state.amountState?.copy(isFeeLoading = false),
feeState = feeState.copy(
feeSelectorState = updatedFeeSelectorState,
fee = fee,
@@ -85,7 +88,6 @@ internal class FeeStateFactory(
fun onFeeOnErrorState(): SendUiState {
val state = currentStateProvider()
return state.copy(
- amountState = state.amountState?.copy(isFeeLoading = false),
feeState = state.feeState?.copy(
feeSelectorState = FeeSelectorState.Error,
),
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
index d9b92b387d..f0cdd28425 100644
--- 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
@@ -14,7 +14,7 @@ internal class SendFeeStateConverter(
override fun convert(value: Unit): SendStates.FeeState {
return SendStates.FeeState(
- feeSelectorState = FeeSelectorState.Error,
+ feeSelectorState = FeeSelectorState.Loading,
fee = null,
notifications = persistentListOf(),
rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate,
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt
index 8d7daa1665..a1fc8fbe25 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt
@@ -38,7 +38,6 @@ internal class SendAmountFieldChangeConverter(
return state.copy(
amountState = amountState.copy(
isPrimaryButtonEnabled = !isExceedBalance && !isZero,
- subtractedFee = null,
amountTextField = amountTextField.copy(
value = cryptoValue,
fiatValue = fiatValue,
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt
index 715d3ad4f8..b29459e757 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt
@@ -34,7 +34,6 @@ internal class SendAmountFieldMaxAmountConverter(
return state.copy(
amountState = amountState.copy(
isPrimaryButtonEnabled = true,
- subtractedFee = null,
amountTextField = amountTextField.copy(
value = cryptoValue,
fiatValue = fiatValue,
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt
index 91dbbde9bf..cbe162c0c5 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt
@@ -22,10 +22,7 @@ internal object AmountStatePreviewData {
walletBalance = stringReference("123.123"),
tokenIconState = TokenIconState.Loading,
segmentedButtonConfig = persistentListOf(),
- notifications = persistentListOf(),
- isFeeLoading = false,
appCurrencyCode = "usd",
- subtractedFee = null,
amountTextField = SendTextField.AmountField(
value = "123.123123123123123123",
onValueChange = {},
@@ -49,6 +46,7 @@ internal object AmountStatePreviewData {
isError = false,
error = TextReference.EMPTY,
),
+ isSegmentedButtonsEnabled = true,
)
val fiatAmountState = amountState.copy(
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt
index 92be46efa2..10a3142e61 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt
@@ -34,14 +34,12 @@ internal object SendClickIntentsStub : SendClickIntents {
override fun onRecipientMemoValueChange(value: String) {}
- override fun feeReload(isToNextState: Boolean) {}
+ override fun feeReload() {}
override fun onFeeSelectorClick(feeType: FeeType) {}
override fun onCustomFeeValueChange(index: Int, value: String) {}
- override fun onSubtractSelect() {}
-
override fun onReadMoreClick() {}
override fun onSendClick() {}
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt
index f6ccb6ba98..3b555a7e21 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt
@@ -4,7 +4,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.utils.DateTimeFormatters
-import com.tangem.core.ui.utils.toDateFormat
+import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
@@ -81,7 +81,7 @@ internal class SendRecipientHistoryListConverter(
}
private fun TxHistoryItem.extractTimestamp(): TextReference {
- val date = timestampInMillis.toDateFormat(
+ val date = timestampInMillis.toDateFormatWithTodayYesterday(
formatter = DateTimeFormatters.dateDDMMYYYY,
)
val time = timestampInMillis.toTimeFormat()
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt
index 855e5d3a3b..6b92680baf 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt
@@ -18,22 +18,25 @@ internal class SendRecipientWalletListConverter :
}
}
- private fun List.filterWallets() = this.filterNotNull()
- .groupBy { item -> item.name }
- .values.map {
- it.mapIndexed { index, item ->
- val name = if (it.size > 1) {
- "${item.name} ${index.inc()}"
- } else {
- item.name
+ private fun List.filterWallets(): PersistentList {
+ var walletsCounter = 0
+ return this.filterNotNull()
+ .groupBy { item -> item.name }
+ .values.map {
+ it.mapIndexed { index, item ->
+ val name = if (it.size > 1) {
+ "${item.name} ${index.inc()}"
+ } else {
+ item.name
+ }
+ SendRecipientListContent(
+ id = "${WALLET_KEY_TAG}${walletsCounter++}",
+ title = TextReference.Str(item.address),
+ subtitle = TextReference.Str(name),
+ )
}
- SendRecipientListContent(
- id = "${WALLET_KEY_TAG}$index",
- title = TextReference.Str(item.address),
- subtitle = TextReference.Str(name),
- )
}
- }
- .flatten()
- .toPersistentList()
+ .flatten()
+ .toPersistentList()
+ }
}
\ 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 fda14b8a8a..604a9ba0a6 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
@@ -78,7 +78,6 @@ private fun SendNavigationButton(
val isFromConfirmation = currentState.isFromConfirmation
val isCorrectScreen = currentState.type == SendUiStateType.Amount || currentState.type == SendUiStateType.Fee
val isSendingState = currentState.type == SendUiStateType.Send && !isSuccess && !isSending
- val showProgress = uiState.amountState?.isFeeLoading == true
val (buttonTextId, buttonClick) = getButtonData(
currentState = currentState,
@@ -120,7 +119,7 @@ private fun SendNavigationButton(
if (isSendingState) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
buttonClick()
},
- showProgress = showProgress,
+ showProgress = false,
modifier = Modifier.fillMaxWidth(),
colors = TangemButtonsDefaults.primaryButtonColors,
)
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt
index 00af1e90e7..340f8e20ad 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt
@@ -28,7 +28,7 @@ private const val AMOUNT_BUTTONS_KEY = "amountButtonsKey"
internal fun LazyListScope.buttons(
segmentedButtonConfig: PersistentList,
clickIntents: SendClickIntents,
- isMaxButtonEnabled: Boolean,
+ isSegmentedButtonsEnabled: Boolean,
) {
item(
key = AMOUNT_BUTTONS_KEY,
@@ -48,15 +48,18 @@ internal fun LazyListScope.buttons(
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
clickIntents.onCurrencyChangeClick(it.isFiat)
},
+ isEnabled = isSegmentedButtonsEnabled,
) {
- SendAmountCurrencyButton(it)
+ SendAmountCurrencyButton(
+ button = it,
+ isSegmentedButtonsEnabled = isSegmentedButtonsEnabled,
+ )
}
} else {
SpacerWMax()
}
SecondaryButton(
text = stringResource(R.string.send_max_amount),
- enabled = isMaxButtonEnabled,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
clickIntents.onMaxValueClick()
@@ -72,7 +75,7 @@ internal fun LazyListScope.buttons(
}
@Composable
-private fun SendAmountCurrencyButton(button: SendAmountSegmentedButtonsConfig) {
+private fun SendAmountCurrencyButton(button: SendAmountSegmentedButtonsConfig, isSegmentedButtonsEnabled: Boolean) {
Row(
modifier = Modifier
.fillMaxSize()
@@ -86,17 +89,16 @@ private fun SendAmountCurrencyButton(button: SendAmountSegmentedButtonsConfig) {
FiatIcon(
url = button.iconUrl,
size = TangemTheme.dimens.size18,
+ isGrayscale = !isSegmentedButtonsEnabled,
modifier = Modifier.size(TangemTheme.dimens.size18),
)
- } else {
- button.iconState?.let {
- TokenIcon(
- state = it,
- shouldDisplayNetwork = false,
- modifier = Modifier
- .size(TangemTheme.dimens.size18),
- )
- }
+ } else if (button.iconState != null) {
+ TokenIcon(
+ state = button.iconState,
+ shouldDisplayNetwork = false,
+ modifier = Modifier
+ .size(TangemTheme.dimens.size18),
+ )
}
Text(
text = button.title.resolveReference(),
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt
index bb5b97aa51..f92a93cdae 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt
@@ -22,11 +22,10 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.rememberDecimalFormat
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
-import kotlinx.coroutines.delay
import kotlinx.coroutines.job
@Composable
-internal fun AmountField(sendField: SendTextField.AmountField, isEnabled: Boolean, appCurrencyCode: String) {
+internal fun AmountField(sendField: SendTextField.AmountField, appCurrencyCode: String) {
val decimalFormat = rememberDecimalFormat()
val isFiatValue = sendField.isFiatValue
val currencyCode = if (isFiatValue) appCurrencyCode else null
@@ -36,15 +35,6 @@ internal fun AmountField(sendField: SendTextField.AmountField, isEnabled: Boolea
sendField.cryptoAmount to sendField.value
}
val requester = remember { FocusRequester() }
- var isEnabledProxy by remember { mutableStateOf(isEnabled) }
-
- // Fix animation from amount screen to summary screen ([REDACTED_TASK_KEY])
- LaunchedEffect(key1 = isEnabled) {
- if (isEnabled) {
- delay(timeMillis = 700)
- }
- isEnabledProxy = isEnabled
- }
AmountTextField(
value = primaryValue,
@@ -62,7 +52,6 @@ internal fun AmountField(sendField: SendTextField.AmountField, isEnabled: Boolea
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
),
- isEnabled = isEnabledProxy,
isAutoResize = true,
modifier = Modifier
.focusRequester(requester)
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt
index d9926e3f54..eb78c8d0a1 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountFieldContainer.kt
@@ -62,7 +62,6 @@ internal fun LazyListScope.amountField(
)
AmountField(
sendField = amountState.amountTextField,
- isEnabled = !amountState.isFeeLoading,
appCurrencyCode = amountState.appCurrencyCode,
)
}
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt
index 0f5b6493e8..6806468b2b 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt
@@ -12,7 +12,6 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.previewdata.AmountStatePreviewData
import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub
-import com.tangem.features.send.impl.presentation.ui.common.notifications
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
@Composable
@@ -35,9 +34,8 @@ internal fun SendAmountContent(
buttons(
segmentedButtonConfig = amountState.segmentedButtonConfig,
clickIntents = clickIntents,
- isMaxButtonEnabled = !amountState.isFeeLoading,
+ isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled,
)
- notifications(amountState.notifications)
}
}
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt
index a3ee42ccbb..11423e7c73 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt
@@ -1,21 +1,22 @@
package com.tangem.features.send.impl.presentation.ui.send
+import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.*
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.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.rows.SelectorRowItem
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
@@ -26,20 +27,12 @@ import com.tangem.features.send.impl.presentation.utils.getFiatReference
@Composable
internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick: () -> Unit) {
- val fee = feeState.fee ?: return
- val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
- val (title, icon) = when (feeSelectorState.selectedFee) {
- FeeType.Slow -> R.string.common_fee_selector_option_slow to R.drawable.ic_tortoise_24
- FeeType.Market -> R.string.common_fee_selector_option_market to R.drawable.ic_bird_24
- FeeType.Fast -> R.string.common_fee_selector_option_fast to R.drawable.ic_hare_24
- FeeType.Custom -> R.string.common_fee_selector_option_custom to R.drawable.ic_edit_24
- }
Column(
modifier = Modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action)
- .clickable(enabled = !isSuccess) { onClick() }
+ .clickable(enabled = !isSuccess && feeState.fee != null) { onClick() }
.padding(TangemTheme.dimens.spacing12),
) {
Text(
@@ -47,17 +40,71 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.secondary,
)
- SelectorRowItem(
- titleRes = title,
- iconRes = icon,
- preDot = getCryptoReference(fee.amount, feeState.isFeeApproximate),
- postDot = getFiatReference(fee.amount, feeState.rate, feeState.appCurrency),
- ellipsizeOffset = fee.amount.currencySymbol.length,
- isSelected = true,
- showDivider = false,
- paddingValues = PaddingValues(),
+
+ Box(
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
- )
+ ) {
+ val feeSelectorState = feeState.feeSelectorState
+ val feeAmount = feeState.fee?.amount
+ val (title, icon) = if (feeSelectorState is FeeSelectorState.Content) {
+ when (feeSelectorState.selectedFee) {
+ FeeType.Slow -> R.string.common_fee_selector_option_slow to R.drawable.ic_tortoise_24
+ FeeType.Market -> R.string.common_fee_selector_option_market to R.drawable.ic_bird_24
+ FeeType.Fast -> R.string.common_fee_selector_option_fast to R.drawable.ic_hare_24
+ FeeType.Custom -> R.string.common_fee_selector_option_custom to R.drawable.ic_edit_24
+ }
+ } else {
+ R.string.common_fee_selector_option_market to R.drawable.ic_bird_24
+ }
+ SelectorRowItem(
+ titleRes = title,
+ iconRes = icon,
+ preDot = getCryptoReference(feeAmount, feeState.isFeeApproximate),
+ postDot = feeAmount?.let { getFiatReference(it, feeState.rate, feeState.appCurrency) },
+ ellipsizeOffset = feeAmount?.currencySymbol?.length,
+ isSelected = true,
+ showDivider = false,
+ paddingValues = PaddingValues(),
+ )
+ FeeLoading(feeSelectorState)
+ FeeError(feeSelectorState)
+ }
+ }
+}
+
+@Composable
+private fun BoxScope.FeeLoading(feeSelectorState: FeeSelectorState) {
+ AnimatedContent(
+ targetState = feeSelectorState,
+ label = "Fee Loading State Change",
+ modifier = Modifier.align(Alignment.CenterEnd),
+ ) {
+ if (it == FeeSelectorState.Loading) {
+ RectangleShimmer(
+ radius = TangemTheme.dimens.radius3,
+ modifier = Modifier.size(
+ height = TangemTheme.dimens.size12,
+ width = TangemTheme.dimens.size90,
+ ),
+ )
+ }
+ }
+}
+
+@Composable
+private fun BoxScope.FeeError(feeSelectorState: FeeSelectorState) {
+ AnimatedContent(
+ targetState = feeSelectorState,
+ label = "Fee Error State Change",
+ modifier = Modifier.align(Alignment.CenterEnd),
+ ) {
+ if (it == FeeSelectorState.Error) {
+ Text(
+ text = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
+ color = TangemTheme.colors.text.primary1,
+ style = TangemTheme.typography.body2,
+ )
+ }
}
}
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt
index f276edf3c1..0801e63cd6 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt
@@ -4,6 +4,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -40,6 +41,7 @@ internal fun RecipientBlock(
.padding(TangemTheme.dimens.spacing12),
) {
AddressBlock(recipientState.addressTextField)
+ MemoBlock(recipientState.memoTextField)
}
}
@@ -70,6 +72,28 @@ private fun AddressBlock(address: SendTextField.RecipientAddress) {
}
}
+@Composable
+private fun MemoBlock(memo: SendTextField.RecipientMemo?) {
+ val showMemo = memo != null && memo.value.isNotBlank()
+ if (showMemo) {
+ HorizontalDivider(
+ color = TangemTheme.colors.icon.inactive,
+ modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12),
+ )
+ Text(
+ text = memo?.label?.resolveReference().orEmpty(),
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.secondary,
+ )
+ Text(
+ text = memo?.value.orEmpty(),
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.primary1,
+ modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
+ )
+ }
+}
+
// region Preview
@Preview
@Composable
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 9db5547b20..74e5d81128 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
@@ -39,14 +39,12 @@ internal interface SendClickIntents {
// endregion
// region Fee
- fun feeReload(isToNextState: Boolean = false)
+ fun feeReload()
fun onFeeSelectorClick(feeType: FeeType)
fun onCustomFeeValueChange(index: Int, value: String)
- fun onSubtractSelect()
-
fun onReadMoreClick()
// endregion
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 ffad3f3633..caec97e044 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
@@ -24,6 +24,7 @@ import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
+import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.utils.convertToAmount
import com.tangem.domain.transaction.error.GetFeeError
@@ -156,10 +157,10 @@ internal class SendViewModel @Inject constructor(
private val sendNotificationFactory = SendNotificationFactory(
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus },
- feePaidCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus },
currentStateProvider = Provider { uiState },
userWalletProvider = Provider { userWallet },
stateRouterProvider = Provider { stateRouter },
+ isSubtractAvailableProvider = Provider { isAmountSubtractAvailable },
currencyChecksRepository = currencyChecksRepository,
clickIntents = this,
analyticsEventHandler = analyticsEventHandler,
@@ -191,7 +192,6 @@ internal class SendViewModel @Inject constructor(
private var qrScannerJobHolder = JobHolder()
private var sendIdleTimer = 0L
- private var feeIdleTimer = 0L
init {
subscribeOnCurrencyStatusUpdates()
@@ -359,13 +359,14 @@ internal class SendViewModel @Inject constructor(
stateRouter.showSend()
}
transactionId != null && amount != null && destinationAddress != null -> {
+ loadFee()
uiState = stateFactory.getReadyState(amount, destinationAddress, memo)
- stateRouter.showFee()
+ stateRouter.showSend()
updateNotifications()
}
else -> {
- getWalletsAndRecent()
uiState = stateFactory.getReadyState()
+ getWalletsAndRecent()
stateRouter.showRecipient()
updateNotifications()
}
@@ -406,7 +407,8 @@ internal class SendViewModel @Inject constructor(
return if (!isMultiCurrency) {
val status = getCryptoCurrencyStatusSyncUseCase(walletId).getOrNull()
val address = status?.value?.networkAddress.takeIf {
- status?.currency?.network?.id == cryptoCurrency.network.id
+ status?.currency?.network?.id == cryptoCurrency.network.id &&
+ status.currency.network.derivationPath !is Network.DerivationPath.Custom
}
address?.let {
AvailableWallet(
@@ -417,7 +419,8 @@ internal class SendViewModel @Inject constructor(
} else {
val statuses = getCryptoCurrencyStatusesSyncUseCase(walletId).getOrNull()
val walletCurrency = statuses?.firstOrNull {
- it.currency.network.id == cryptoCurrency.network.id
+ it.currency.network.id == cryptoCurrency.network.id &&
+ it.currency.network.derivationPath !is Network.DerivationPath.Custom
}
val address = walletCurrency?.value?.networkAddress
address?.let {
@@ -484,12 +487,7 @@ internal class SendViewModel @Inject constructor(
if (onFeeNext()) return
}
SendUiStateType.Amount -> {
- if (uiState.feeState?.feeSelectorState is FeeSelectorState.Content) {
- if (onFeeCoverageAlert()) return
- } else {
- loadFee(isToNextState = true)
- return
- }
+ loadFee()
}
else -> Unit
}
@@ -515,15 +513,15 @@ internal class SendViewModel @Inject constructor(
innerRouter.openTokenDetails(userWalletId, currency)
private fun onFeeNext(): Boolean {
- if (onFeeCoverageAlert()) return true
if (checkIfFeeTooLow(uiState)) {
uiState = eventStateFactory.getFeeTooLowAlert(
onConsume = { uiState = eventStateFactory.onConsumeEventState() },
)
return true
}
+ val feeSelectorState = uiState.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false
return checkIfFeeTooHigh(
- state = uiState,
+ feeSelectorState = feeSelectorState,
onShow = { diff ->
uiState = eventStateFactory.getFeeTooHighAlert(
diff = diff,
@@ -533,19 +531,6 @@ internal class SendViewModel @Inject constructor(
)
}
- private fun onFeeCoverageAlert(): Boolean {
- val isFeeCoverage = checkFeeCoverage(uiState, cryptoCurrencyStatus)
- return if (isAmountSubtractAvailable && isFeeCoverage) {
- uiState = eventStateFactory.getFeeCoverageAlert(
- onConsume = { uiState = eventStateFactory.onConsumeEventState() },
- )
- true
- } else {
- analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount(false))
- false
- }
- }
-
private fun cancelFeeRequest() {
viewModelScope.launch(dispatchers.main) {
feeJobHolder.cancel()
@@ -647,7 +632,7 @@ internal class SendViewModel @Inject constructor(
// endregion
// region fee
- override fun feeReload(isToNextState: Boolean) = loadFee(isToNextState = isToNextState)
+ override fun feeReload() = loadFee()
override fun onFeeSelectorClick(feeType: FeeType) {
uiState = feeStateFactory.onFeeSelectedState(feeType)
@@ -662,12 +647,6 @@ internal class SendViewModel @Inject constructor(
updateFeeNotifications()
}
- override fun onSubtractSelect() {
- uiState = stateFactory.onSubtractSelect(isAmountSubtractAvailable)
- stateRouter.showSend()
- analyticsEventHandler.send(SendAnalyticEvents.SubtractFromAmount(true))
- }
-
override fun onReadMoreClick() {
val locale = if (Locale.getDefault().language == RU_LOCALE) RU_LOCALE else EN_LOCALE
val url = buildString {
@@ -678,10 +657,7 @@ internal class SendViewModel @Inject constructor(
innerRouter.openUrl(url)
}
- private fun loadFee(isToNextState: Boolean = false) {
- // debouncing fee request
- if (SystemClock.elapsedRealtime() - feeIdleTimer < FEE_UPDATE_DELAY) return
-
+ private fun loadFee() {
viewModelScope.launch(dispatchers.main) {
val isShowStatus = uiState.feeState?.fee == null
if (isShowStatus) {
@@ -689,35 +665,25 @@ internal class SendViewModel @Inject constructor(
}
val result = callFeeUseCase()?.fold(
ifRight = {
- feeIdleTimer = SystemClock.elapsedRealtime()
uiState = feeStateFactory.onFeeOnLoadedState(it)
- if (isToNextState && !onFeeCoverageAlert()) {
- stateRouter.showSend()
- }
},
ifLeft = {
- onFeeLoadFailed(isShowStatus, isToNextState)
+ onFeeLoadFailed(isShowStatus)
},
)
if (result == null) {
- onFeeLoadFailed(isShowStatus, isToNextState)
+ onFeeLoadFailed(isShowStatus)
}
+ updateNotifications()
updateFeeNotifications()
}.saveIn(feeJobHolder)
.invokeOnCompletion {
- uiState = amountStateFactory.getOnAmountFeeLoadingCancel()
+ // todo
}
}
- private fun onFeeLoadFailed(isShowStatus: Boolean, isToNextState: Boolean) {
- when {
- isToNextState -> {
- uiState = eventStateFactory.getFeeUnreachableErrorState {
- uiState = eventStateFactory.onConsumeEventState()
- }
- }
- isShowStatus -> uiState = feeStateFactory.onFeeOnErrorState()
- }
+ private fun onFeeLoadFailed(isShowStatus: Boolean) {
+ if (isShowStatus) uiState = feeStateFactory.onFeeOnErrorState()
}
private suspend fun checkIfSubtractAvailable() {
@@ -735,7 +701,7 @@ internal class SendViewModel @Inject constructor(
return getFeeUseCase.invoke(
amount = amount,
destination = recipientState.addressTextField.value,
- userWalletId = userWalletId,
+ userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
)
}
@@ -751,7 +717,6 @@ internal class SendViewModel @Inject constructor(
verifyAndSendTransaction()
} else {
onCheckFeeUpdate()
- feeIdleTimer = SystemClock.elapsedRealtime()
sendIdleTimer = SystemClock.elapsedRealtime()
}
}
@@ -792,10 +757,11 @@ internal class SendViewModel @Inject constructor(
override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) {
uiState = amountStateFactory.getOnAmountValueChange(reducedAmount.parseBigDecimal(cryptoCurrency.decimals))
uiState = sendNotificationFactory.dismissNotificationState(clazz)
+ feeReload()
}
override fun onNotificationCancel(clazz: Class) {
- uiState = sendNotificationFactory.dismissNotificationState(clazz)
+ uiState = sendNotificationFactory.dismissNotificationState(clazz = clazz, isIgnored = true)
}
private fun verifyAndSendTransaction() {
@@ -804,10 +770,18 @@ internal class SendViewModel @Inject constructor(
val fee = feeState.fee ?: return
val memo = uiState.recipientState?.memoTextField?.value
val amountValue = uiState.amountState?.amountTextField?.cryptoAmount?.value ?: return
+ val feeValue = fee.amount.value ?: return
+
+ val receivingAmount = checkAndCalculateSubtractedAmount(
+ isAmountSubtractAvailable = isAmountSubtractAvailable,
+ cryptoCurrencyStatus = cryptoCurrencyStatus,
+ amountValue = amountValue,
+ feeValue = feeValue,
+ )
viewModelScope.launch(dispatchers.main) {
createTransactionUseCase(
- amount = amountValue.convertToAmount(cryptoCurrency),
+ amount = receivingAmount.convertToAmount(cryptoCurrency),
fee = fee,
memo = memo,
destination = recipient,
@@ -920,7 +894,6 @@ internal class SendViewModel @Inject constructor(
private companion object {
const val CHECK_FEE_UPDATE_DELAY = 60_000L
- const val FEE_UPDATE_DELAY = 10_000L
const val BALANCE_UPDATE_DELAY = 10_000L
const val RU_LOCALE = "ru"
diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt
index a4b2c646ab..fc191d1d2d 100644
--- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt
+++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt
@@ -124,14 +124,15 @@ class SwapDomainModule {
@Provides
@Singleton
fun provideSendTransactionUseCase(
- @SwapScope isDemoCardUseCase: IsDemoCardUseCase,
cardSdkConfigRepository: CardSdkConfigRepository,
transactionRepository: TransactionRepository,
+ walletManagersFacade: WalletManagersFacade,
): SendTransactionUseCase {
return SendTransactionUseCase(
- isDemoCardUseCase = isDemoCardUseCase,
+ demoConfig = DemoConfig(),
cardSdkConfigRepository = cardSdkConfigRepository,
transactionRepository = transactionRepository,
+ walletManagersFacade = walletManagersFacade,
)
}
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
index a74ac1059c..c3fd4f4fd5 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
@@ -646,6 +646,7 @@ internal class StateBuilder(
}
private fun getWarningForError(dataError: DataError, fromToken: CryptoCurrency): SwapWarning {
+ val providerErrorMessage = getProviderErrorMessage(dataError)
return when (dataError) {
is DataError.ExchangeTooSmallAmountError -> SwapWarning.GeneralError(
notificationConfig = NotificationConfig(
@@ -674,10 +675,16 @@ internal class StateBuilder(
} else {
resourceReference(R.string.warning_express_refresh_required_title)
},
- subtitle = if (dataError is DataError.UnknownError) {
- resourceReference(R.string.common_unknown_error)
- } else {
- resourceReference(R.string.express_error_code, wrappedList(dataError.code.toString()))
+ subtitle = when {
+ dataError is DataError.UnknownError -> {
+ resourceReference(R.string.common_unknown_error)
+ }
+ providerErrorMessage != null -> {
+ providerErrorMessage
+ }
+ else -> {
+ resourceReference(R.string.express_error_code, wrappedList(dataError.code.toString()))
+ }
},
iconResId = R.drawable.img_attention_20,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
@@ -972,7 +979,7 @@ internal class StateBuilder(
return uiState.copy(
alert = SwapWarning.GenericWarning(
message = if (swapTransactionState is SwapTransactionState.ExpressError) {
- getAlertErrorMessage(swapTransactionState.dataError)
+ getProviderErrorMessage(swapTransactionState.dataError)
} else {
null
},
@@ -987,7 +994,7 @@ internal class StateBuilder(
)
}
- private fun getAlertErrorMessage(dataError: DataError): TextReference? {
+ private fun getProviderErrorMessage(dataError: DataError): TextReference? {
return when (dataError) {
is DataError.SwapsAreUnavailableNowError -> resourceReference(
id = R.string.express_error_swap_unavailable,
@@ -998,6 +1005,7 @@ internal class StateBuilder(
formatArgs = wrappedList(dataError.code),
)
is DataError.ExchangeProviderNotActiveError,
+ is DataError.ExchangeProviderNotFoundError,
is DataError.ExchangeProviderNotAvailableError,
is DataError.ExchangeProviderProviderInternalError,
-> resourceReference(
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt
index d5445e3d1a..55e6cba9b0 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt
@@ -4,7 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.utils.BigDecimalFormatter
-import com.tangem.core.ui.utils.toDateFormat
+import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrency
@@ -71,7 +71,7 @@ internal class TokenDetailsSwapTransactionsStateConverter(
txUrl = transaction.status?.txExternalUrl,
txExternalId = transaction.status?.txExternalId,
timestamp = TextReference.Str(
- "${timestamp.toDateFormat()}, ${timestamp.toTimeFormat()}",
+ "${timestamp.toDateFormatWithTodayYesterday()}, ${timestamp.toTimeFormat()}",
),
fiatSymbol = appCurrency.symbol,
statuses = getStatuses(transaction.status?.status),
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt
index 9fb6df70b1..6a8bc163c7 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryItemFlowConverter.kt
@@ -4,7 +4,7 @@ import androidx.paging.*
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState
-import com.tangem.core.ui.utils.toDateFormat
+import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
@@ -68,7 +68,7 @@ internal class TokenDetailsTxHistoryItemFlowConverter(
// Use raw timestamp to get date
// If [afterDate] is the first transaction in the flow, add the group title
- val afterDate = after.getTimestamp()?.toDateFormat() ?: return@insertSeparators null
+ val afterDate = after.getTimestamp()?.toDateFormatWithTodayYesterday() ?: return@insertSeparators null
if (before is TxHistoryItemState.Title) {
return@insertSeparators TxHistoryItemState.GroupTitle(
title = afterDate,
@@ -80,7 +80,7 @@ internal class TokenDetailsTxHistoryItemFlowConverter(
* If [beforeDate] is not equals to [afterDate], then [afterDate] is first transaction in
* the new group
*/
- val beforeDate = before.getTimestamp()?.toDateFormat() ?: return@insertSeparators null
+ val beforeDate = before.getTimestamp()?.toDateFormatWithTodayYesterday() ?: return@insertSeparators null
return@insertSeparators if (beforeDate != afterDate) {
TxHistoryItemState.GroupTitle(
title = afterDate,
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt
index 5722777eb6..26d69cf20d 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt
@@ -10,7 +10,6 @@ import com.tangem.blockchain.common.address.AddressType
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.deeplink.global.BuyCurrencyDeepLink
-import com.tangem.core.deeplink.global.SellCurrencyDeepLink
import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.extensions.resourceReference
@@ -166,7 +165,6 @@ internal class TokenDetailsViewModel @Inject constructor(
viewModel = this,
deepLinks = listOf(
BuyCurrencyDeepLink(::onBuyCurrencyDeepLink),
- SellCurrencyDeepLink(::onSellCurrencyDeepLink),
),
)
}
@@ -176,20 +174,6 @@ internal class TokenDetailsViewModel @Inject constructor(
analyticsEventsHandler.send(TokenScreenAnalyticsEvent.Bought(currency.symbol))
}
- private fun onSellCurrencyDeepLink(data: SellCurrencyDeepLink.Data) {
- sendCurrency(
- status = cryptoCurrencyStatus ?: return,
- transactionInfo = data.let {
- TransactionInfo(
- transactionId = it.transactionId,
- destinationAddress = it.depositWalletAddress,
- amount = it.baseCurrencyAmount,
- tag = it.depositWalletAddressTag,
- )
- },
- )
- }
-
override fun onCreate(owner: LifecycleOwner) {
analyticsEventsHandler.send(
event = TokenScreenAnalyticsEvent.DetailsScreenOpened(token = cryptoCurrency.symbol),
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt
index 59e2022b58..3c1e89fbeb 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt
@@ -8,16 +8,14 @@ import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.deeplink.global.BuyCurrencyDeepLink
import com.tangem.core.deeplink.global.SellCurrencyDeepLink
import com.tangem.domain.redux.ReduxStateHolder
-import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
-import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
-import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
-import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase
+import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent
import com.tangem.domain.wallets.models.UserWallet
+import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
@@ -29,23 +27,20 @@ internal class WalletDeepLinksHandler @Inject constructor(
private val analyticsEventHandler: AnalyticsEventHandler,
private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase,
private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase,
+ private val getCryptoCurrencyStatusesSyncUseCase: GetCryptoCurrencyStatusesSyncUseCase,
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase,
private val reduxStateHolder: ReduxStateHolder,
) {
- private var deepLinks: List = emptyList()
+ private var deepLinksMap = mutableMapOf>()
- fun registerForSingleCurrencyWallets(viewModel: ViewModel, userWallet: UserWallet) {
- if (userWallet.isMultiCurrency) {
- deepLinksRegistry.unregister(deepLinks)
- } else {
- if (deepLinks.isEmpty()) {
- deepLinks = getDeepLinks(userWallet, viewModel.viewModelScope)
- }
-
- deepLinksRegistry.register(deepLinks)
+ fun registerForWallet(viewModel: ViewModel, userWallet: UserWallet) {
+ val deepLinks = deepLinksMap.getOrPut(userWallet.walletId) {
+ getDeepLinks(userWallet, viewModel.viewModelScope)
}
+ deepLinksRegistry.unregisterByIds(deepLinks.map { it.id })
+ deepLinksRegistry.register(deepLinks)
viewModel.addCloseable {
deepLinksRegistry.unregister(deepLinks)
@@ -60,20 +55,23 @@ internal class WalletDeepLinksHandler @Inject constructor(
}
},
)
- val buyCurrencyDeepLink = BuyCurrencyDeepLink(
- onReceive = {
- scope.launch {
- onBuyCurrencyDeepLink(userWallet)
- }
- },
- )
- return listOf(sellCurrencyDeepLink, buyCurrencyDeepLink)
+ return buildList {
+ add(sellCurrencyDeepLink)
+ if (!userWallet.isMultiCurrency) {
+ add(
+ BuyCurrencyDeepLink(
+ onReceive = {
+ scope.launch { onBuyCurrencyDeepLink(userWallet) }
+ },
+ ),
+ )
+ }
+ }
}
private suspend fun onSellCurrencyDeepLink(userWallet: UserWallet, data: SellCurrencyDeepLink.Data) {
- val cryptoCurrencyStatus = getCryptoCurrencyStatusSyncUseCase(userWallet.walletId)
- .getOrNull() ?: return
+ val cryptoCurrencyStatus = findCryptoCurrencyStatus(userWallet, data.currencyId) ?: return
val feeCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWallet.walletId,
cryptoCurrencyStatus,
@@ -110,6 +108,19 @@ internal class WalletDeepLinksHandler @Inject constructor(
analyticsEventHandler.send(TokenScreenAnalyticsEvent.Bought(cryptoCurrency.symbol))
}
+ private suspend fun findCryptoCurrencyStatus(
+ userWallet: UserWallet,
+ currencyIdValue: String,
+ ): CryptoCurrencyStatus? {
+ return if (userWallet.isMultiCurrency) {
+ getCryptoCurrencyStatusesSyncUseCase(userWallet.walletId).getOrNull()?.let { currencies ->
+ currencies.find { currencyIdValue == it.currency.id.value }
+ }
+ } else {
+ getCryptoCurrencyStatusSyncUseCase(userWallet.walletId).getOrNull()
+ }
+ }
+
private fun sendCoin(
userWallet: UserWallet,
cryptoCurrencyStatus: CryptoCurrencyStatus,
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt
index 62c49d5586..b24e902bfa 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemFlowConverter.kt
@@ -4,7 +4,7 @@ import androidx.paging.*
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState
-import com.tangem.core.ui.utils.toDateFormat
+import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
import com.tangem.utils.converter.Converter
import kotlinx.coroutines.CoroutineScope
@@ -50,7 +50,7 @@ internal class TxHistoryItemFlowConverter(
// Use raw timestamp to get date
// If [afterDate] is the first transaction in the flow, add the group title
- val afterDate = after.getTimestamp()?.toDateFormat() ?: return@insertSeparators null
+ val afterDate = after.getTimestamp()?.toDateFormatWithTodayYesterday() ?: return@insertSeparators null
if (before is TxHistoryItemState.Title) {
return@insertSeparators TxHistoryItemState.GroupTitle(afterDate, itemKey = UUID.randomUUID().toString())
}
@@ -59,7 +59,7 @@ internal class TxHistoryItemFlowConverter(
* If [beforeDate] is not equals to [afterDate], then [afterDate] is first transaction in
* the new group
*/
- val beforeDate = before.getTimestamp()?.toDateFormat() ?: return@insertSeparators null
+ val beforeDate = before.getTimestamp()?.toDateFormatWithTodayYesterday() ?: return@insertSeparators null
return@insertSeparators if (beforeDate != afterDate) {
TxHistoryItemState.GroupTitle(afterDate, itemKey = UUID.randomUUID().toString())
} else {
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt
index eb6f9dc8b2..e74ecbdb0b 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt
@@ -140,7 +140,7 @@ internal class WalletViewModel @Inject constructor(
selectedWalletAnalyticsSender.send(selectedWallet)
}
- walletDeepLinksHandler.registerForSingleCurrencyWallets(
+ walletDeepLinksHandler.registerForWallet(
viewModel = this,
userWallet = selectedWallet,
)
diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt
index cf10f1ce54..a4492852b0 100644
--- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt
+++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt
@@ -33,8 +33,15 @@ object BlockchainUtils {
return blockchain == Blockchain.Bitcoin || blockchain == Blockchain.BitcoinTestnet
}
+ /** If current [networkId] is Dogecoin */
fun isDogecoin(networkId: String): Boolean {
val blockchain = Blockchain.fromId(networkId)
return blockchain == Blockchain.Dogecoin
}
+
+ /** If current [networkId] is Tezos */
+ fun isTezos(networkId: String): Boolean {
+ val blockchain = Blockchain.fromId(networkId)
+ return blockchain == Blockchain.Tezos
+ }
}
\ No newline at end of file