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 dd10c08389..e85db98c7b 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/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 84088baede..7da3c6d73d 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": "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED", diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 5d25d41f8e..5d5c08ef1a 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -51,6 +51,10 @@ Заводские настройки Тип безопасности Настройки карты + Ввиду особенностей сети Cardano при транзакции токена %1$s помимо комиссии сети будет списано %2$s + Для совершения транзакции %1$s, вам необходимо внести немного %2$s (%3$s), чтобы покрыть комиссию сети и минимальное значение ADA для отправки. + Недостаточно ADA для отправки токена + Вывод всего баланса ADA невозможен при наличии средств на токенах сети Cardano. Сначала выведите средства на ваших токенах. Принять Доступ запрещен Применить @@ -96,7 +100,8 @@ Нет адреса OK Основная карта - Кодовая фраза + Парольная фраза + Вставить Подробнее Получить Отклонить @@ -183,9 +188,9 @@ Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами Выберите провайдера Произошла ошибка. Код: %s - К сожалению обмен указанной пары через выбранного провайдера на данный момент не возможен. Попробуйте совершить обмен позже. (Код: %s) + К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) Выбранный провайдер не доступен для обмена. Попробуйте позже. (Код: %s) - В данный момент обмен не возможен. Попробуйте позже. (Код: %s) + В данный момент обмен невозможен. Попробуйте позже. (Код: %s) Курс обмена Обмен через %s Чтобы вернуть ваши деньги, посетите сайт провайдера @@ -288,7 +293,7 @@ Ошибка активации Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. - Кодовая фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. + Парольная фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. Добавить резервную карту Сканировать карту #%d Создать резервную копию @@ -478,7 +483,7 @@ Возможны задержки по транзакции Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. Лимит транзакции - Необязательное + Опционально Пожалуйста, совместите свой QR-код с квадратом, чтобы отсканировать его. Убедитесь, что вы сканируете адрес в сети %s. Последние Получатель diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index a099c577d4..04985255c2 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -50,6 +50,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 @@ -96,6 +100,7 @@ OK Primary Card Passphrase + Paste Read more Receive Reject @@ -427,6 +432,8 @@ 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 +481,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 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/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/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..0938d051f2 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 @@ -117,6 +117,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, ) 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..eb0e05e900 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,6 +48,7 @@ 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, 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..ba3ab64dcf 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, @@ -42,22 +43,23 @@ internal class SendAmountStateConverter( 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 index bf0f52dd73..fb5d2708ad 100644 --- 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 @@ -19,11 +19,13 @@ internal class SendAmountSubtractConverter( val feeValue = feeState.fee?.amount?.value ?: return state val amountTextField = amountState.amountTextField val amountValue = amountTextField.cryptoAmount.value ?: return state + val balance = cryptoCurrencyStatus.value.amount ?: 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 feeDiff = amountState.subtractedFee?.let { feeValue.minus(it) } + ?: amountValue.minus(balance.minus(feeValue)) val decimalCryptoValue = amountValue.minus(feeDiff) if (decimalCryptoValue < BigDecimal.ZERO) return state 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 0d147eb6fd..3ad03b434c 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 @@ -20,10 +20,10 @@ 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.checkIfFeeTooHigh import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.lib.crypto.BlockchainUtils.isDogecoin 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,7 +31,6 @@ 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( @@ -57,6 +56,7 @@ internal class SendNotificationFactory( val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO buildList { // errors + addFeeUnreachableNotification(feeState.feeSelectorState) addExceedBalanceNotification(feeAmount, amountValue) addExceedsBalanceNotification(feeState.fee) addMinimumAmountErrorNotification(feeAmount, amountValue) @@ -70,7 +70,7 @@ internal class SendNotificationFactory( }.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 +78,35 @@ 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 showNotification = if (cryptoCurrencyStatus.currency.id == feePaidCryptoCurrencyStatus?.currency?.id) { - receivedAmount > cryptoAmount || feeAmount > coinCryptoAmount - } else { - receivedAmount + feeAmount > cryptoAmount - } + val isCurrentNotFeePaidCurrency = cryptoCurrencyStatus.currency.id != feePaidCryptoCurrencyStatus?.currency?.id + if (isCurrentNotFeePaidCurrency) return + val showNotification = receivedAmount + feeAmount > cryptoAmount 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 +183,7 @@ internal class SendNotificationFactory( if (currencyDeposit != null && currencyDeposit > diff) { add( SendNotification.Error.ExistentialDeposit( - BigDecimalFormatter.formatCryptoAmount( + BigDecimalFormatter.formatCryptoAmountUncapped( cryptoAmount = currencyDeposit, cryptoCurrency = cryptoCurrency, ), @@ -233,6 +218,20 @@ internal class SendNotificationFactory( } } + 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 +240,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 +267,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 +350,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..31307124fd 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,10 +1,12 @@ package com.tangem.features.send.impl.presentation.state.fee import com.tangem.blockchain.common.transaction.TransactionFee +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 java.math.BigDecimal +import java.math.RoundingMode /** * Check if sending amount with fee is greater than balance @@ -32,14 +34,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..5e34a54d61 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 @@ -47,6 +47,11 @@ internal class FeeStateFactory( 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, ), 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/previewdata/AmountStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt index 91dbbde9bf..c301029957 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 @@ -49,6 +49,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/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/amount/AmountButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountButtons.kt index 00af1e90e7..4e05fe9506 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 @@ -29,6 +29,7 @@ internal fun LazyListScope.buttons( segmentedButtonConfig: PersistentList, clickIntents: SendClickIntents, isMaxButtonEnabled: Boolean, + isSegmentedButtonsEnabled: Boolean, ) { item( key = AMOUNT_BUTTONS_KEY, @@ -48,8 +49,12 @@ internal fun LazyListScope.buttons( hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) clickIntents.onCurrencyChangeClick(it.isFiat) }, + isEnabled = isSegmentedButtonsEnabled, ) { - SendAmountCurrencyButton(it) + SendAmountCurrencyButton( + button = it, + isSegmentedButtonsEnabled = isSegmentedButtonsEnabled, + ) } } else { SpacerWMax() @@ -72,7 +77,7 @@ internal fun LazyListScope.buttons( } @Composable -private fun SendAmountCurrencyButton(button: SendAmountSegmentedButtonsConfig) { +private fun SendAmountCurrencyButton(button: SendAmountSegmentedButtonsConfig, isSegmentedButtonsEnabled: Boolean) { Row( modifier = Modifier .fillMaxSize() @@ -86,17 +91,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/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt index 0f5b6493e8..fb9d2df22c 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 @@ -36,6 +36,7 @@ internal fun SendAmountContent( 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/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index ffad3f3633..77708a0eda 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 @@ -359,13 +360,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 +408,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 +420,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 { @@ -522,8 +526,9 @@ internal class SendViewModel @Inject constructor( ) 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, @@ -702,6 +707,7 @@ internal class SendViewModel @Inject constructor( if (result == null) { onFeeLoadFailed(isShowStatus, isToNextState) } + updateNotifications() updateFeeNotifications() }.saveIn(feeJobHolder) .invokeOnCompletion { @@ -735,7 +741,7 @@ internal class SendViewModel @Inject constructor( return getFeeUseCase.invoke( amount = amount, destination = recipientState.addressTextField.value, - userWalletId = userWalletId, + userWallet = userWallet, cryptoCurrency = cryptoCurrency, ) } @@ -792,10 +798,11 @@ internal class SendViewModel @Inject constructor( override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) { uiState = amountStateFactory.getOnAmountValueChange(reducedAmount.parseBigDecimal(cryptoCurrency.decimals)) uiState = sendNotificationFactory.dismissNotificationState(clazz) + updateNotifications() } override fun onNotificationCancel(clazz: Class) { - uiState = sendNotificationFactory.dismissNotificationState(clazz) + uiState = sendNotificationFactory.dismissNotificationState(clazz = clazz, isIgnored = true) } private fun verifyAndSendTransaction() { 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 196d529e7b..afb2f253a4 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 @@ -126,14 +126,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 eaf4ddfa6b..d8edc6401c 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 f4c38aca5c..02defbde3a 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 @@ -165,7 +164,6 @@ internal class TokenDetailsViewModel @Inject constructor( viewModel = this, deepLinks = listOf( BuyCurrencyDeepLink(::onBuyCurrencyDeepLink), - SellCurrencyDeepLink(::onSellCurrencyDeepLink), ), ) } @@ -175,20 +173,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 63db20714d..369ec32691 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 @@ -154,7 +154,7 @@ internal class WalletViewModel @Inject constructor( selectedWalletAnalyticsSender.send(selectedWallet) } - walletDeepLinksHandler.registerForSingleCurrencyWallets( + walletDeepLinksHandler.registerForWallet( viewModel = this, userWallet = selectedWallet, ) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 56d6351be9..eec9a20c7b 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-600" +tangemBlockchainSdk = "release-app_5.9-605" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^