From 32a94880874570d7c23232269e36ad4224b6c926 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Sep 2024 18:34:59 +0300 Subject: [PATCH 01/18] Updated on 2026-08-14 --- .../amplitude/AmplitudeAnalyticsHandler.kt | 14 ++++++---- core/res/src/main/res/values-de/strings.xml | 2 +- core/res/src/main/res/values-es/strings.xml | 1 + core/res/src/main/res/values-fr/strings.xml | 1 + core/res/src/main/res/values-ja/strings.xml | 5 +++- core/res/src/main/res/values-ru/strings.xml | 11 +++++--- .../src/main/res/values-uk-rUA/strings.xml | 1 + core/res/src/main/res/values/strings.xml | 10 ++++--- .../components/inputrow/InputRowImageBase.kt | 6 +++-- .../components/inputrow/InputRowImageInfo.kt | 3 +++ .../inputrow/InputRowImageSelector.kt | 3 +++ .../inputrow/inner/InputRowAsyncImage.kt | 10 +++++-- .../res/drawable/ic_staking_filled_18.xml | 9 +++++++ .../AddStakingNotificationsTransformer.kt | 8 +++--- .../SetConfirmationStateLoadingTransformer.kt | 8 +++--- .../SetInitialDataStateTransformer.kt | 7 +---- .../ui/StakingClaimRewardsValidatorContent.kt | 1 + .../ui/StakingInitialInfoContent.kt | 4 +-- .../ui/StakingValidatorListContent.kt | 6 ++--- .../ui/ValidatorImagePlaceholder.kt | 25 ++++++++++++++++++ .../presentation/ui/block/ValidatorBlock.kt | 2 ++ .../TokenDetailsLoadedBalanceConverter.kt | 3 +-- .../factory/TokenStakingStateConverter.kt | 3 +-- .../state/utils/RewardScheduleUtils.kt | 26 ------------------- .../presentation/wallet/ui/WalletScreen.kt | 16 ++++++++++-- gradle/dependencies.toml | 2 +- 26 files changed, 113 insertions(+), 74 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_staking_filled_18.xml create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/ValidatorImagePlaceholder.kt delete mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/utils/RewardScheduleUtils.kt diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt index 5eed564c97..2aa36c5413 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt @@ -18,10 +18,14 @@ class AmplitudeAnalyticsHandler( } class Builder : AnalyticsHandlerBuilder { - override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when { - !data.isDebug -> AmplitudeClient(data.application, data.config.amplitudeApiKey) - data.isDebug && data.logConfig.amplitude -> AmplitudeLogClient(data.jsonConverter) - else -> null - }?.let { AmplitudeAnalyticsHandler(it) } + override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler { + return AmplitudeAnalyticsHandler( + client = if (data.logConfig.amplitude) { + AmplitudeLogClient(data.jsonConverter) + } else { + AmplitudeClient(data.application, data.config.amplitudeApiKey) + }, + ) + } } } \ No newline at end of file diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index c130e68816..a72160333d 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -383,6 +383,7 @@ Webseite Kaufdruck Die Differenz zwischen Käufer- und Verkäufervolumen + Kaufdruck Umlaufmenge Die Gesamtzahl der Coins, die für den Handel verfügbar sind und auf dem Markt zirkulieren Erfahrene Käufer @@ -707,7 +708,6 @@ Block Tag Täglich - Jeder %1$s Epoche Ära Stunde diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 3c8fb3e199..0bfcf8de3d 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -382,6 +382,7 @@ Sitio web Presión de compra La diferencia entre el volumen de compradores y el volumen de vendedores + Presión de compra Suministro circulante El número total de monedas que están disponibles para el comercio y que circulan en el mercado. Compradores experimentados diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index d2745aa359..0d3cb6b2bc 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -382,6 +382,7 @@ Site web Pression d\'achat La différence entre le volume des acheteurs et le volume des vendeurs + Pression d\'achat Approvisionnement en circulation Le nombre total de pièces disponibles pour le trading et en circulation sur le marché Acheteurs expérimentés diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 1a848e9582..cff94ccc70 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -353,6 +353,7 @@ データを読み込めません… データなし クイックアクション + マーケットから探す 結果 時価総額10万ドル以下のトークンを見る トークンを表示 @@ -379,6 +380,7 @@ ウェブサイト 買い圧力 買い手と売り手の取引量の差 + 買い圧力 循環供給量 取引可能で市場に流通しているコインの総数 経験豊富な買い手 @@ -697,7 +699,6 @@ ブロック 毎日 - %1$s毎 エポック 時代 @@ -857,6 +858,8 @@ アクティベーションに失敗しました BNBネットワーク開発者によると、BEP-2規格のサポートは2024年6月に終了します。この規格の資産を失わないために、BEP-20規格に変換してください。BNBスマートチェーンネットワークへ移行するには、Tangemのスワップサービスをご利用ください。 BNBビーコンチェーンは閉鎖されます。 + ネットワーク手数料をカバーするために%1$sを入金してください + ネットワーク手数料をカバーする資金が不足しています もっと良くなるはず 気に入った はい、わかりました! diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index b64757fd46..fe50c2b14d 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -38,7 +38,7 @@ Слишком много попыток Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона. Начать резервное копирование - С помощью банковской карты или банковского аккаунта + Используйте банковскую карту или другие методы оплаты %d карта %d карты @@ -276,7 +276,7 @@ Токены не найдены. Пожалуйста, попробуйте другой запрос ID: %s ID транзакции скопирован - С другой валютой в вашем кошельке + Конвертируйте одну из ваших валют в другую Информация ниже не является обязательной. Вы можете стереть её, если хотите. Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. Скажите, пожалуйста, какая у вас карта? @@ -352,6 +352,7 @@ Мой портфель Рынок Чтобы создать адреса для выбранных сетей, отсканируйте вашу карту Tangem кошелька + Потяните вверх или коснитесь поисковой строки, чтобы добавить токен Данные раздела получены из следующих сетей: %s Невозможно загрузить данные Нет данных @@ -386,6 +387,7 @@ Веб-сайт Покуп. предпоч. Разница между объемом покупателей и продавцов + Покуп. предпоч. Циркулир. предл. Общее количество монет, доступных для торговли и находящихся в обращении на рынке Опытные трейдеры @@ -524,7 +526,7 @@ Доступ к камере запрещен %1$s (%2$s) в сети %3$s Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств. - Покажи QR-код или поделись своим адресом + Переводите средства с другого кошелька или биржи Участвовать Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. @@ -654,6 +656,7 @@ Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова. Имя Сумма для стейкинга должна быть не менее %s + Согласно правилам сети, сумма стейкинга будет округлена до %1$sTRX. Забрать средства Стейкинг аккаунт — это специальный счет, на котором хранятся застейканные токены SOL. Он создается при делегировании ваших токенов валидатору для участия в подтверждении транзакций и получении наград. За создание стейкинг аккаунта взимается небольшая комиссия, которая возвращается после завершения стейкинга. Процентная ставка @@ -699,7 +702,7 @@ Блок День Каждый день - Каждые ~%1$s сек + Каждую минуту Эпоха Эра Час diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 9e4b55bc64..c032f68459 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -391,6 +391,7 @@ Веб-сайт Давлення покупця Різниця між обсягом покупців та обсягом продавців + Давлення покупця Циркуляційний запас Загальна кількість монет, які доступні для торгівлі та перебувають в обігу на ринку Досвідчені покупці diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index b3ac1d05be..e95c0bc2b7 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -39,7 +39,7 @@ Too many attempts You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings. Start backup process - With your bank card or bank account + Use a bank card or other payment methods %d card %d cards @@ -275,7 +275,7 @@ No tokens found. Please try another request ID: %s Transaction ID copied - With another currency in your wallet + Convert one of your currencies to another The following information is optional. You can erase it if you don\'t want to share it. Tell us what functions you are missing, and we will try to help you. Please tell us what card do you have @@ -384,6 +384,7 @@ Website Buy pressure The difference between buyers volume and sellers volume + Buy pressure Circulating supply The total number of coins that are available for trading and are circulating in the market Experienced buyers @@ -518,7 +519,7 @@ Camera access denied %1$s (%2$s) on %3$s network Send only %s to this address. Sending any other currency will result in its irreversible loss. - Show a QR-code or share your address + Transfer funds from another wallet or exchange Participate Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Error code: %s. Please try again later. @@ -678,6 +679,7 @@ Migrate Native staking Earned rewards will be sent to your wallet and available for use immediately + Stake securely and start earning your rewards Stake securely and start earning daily rewards Stake securely and start earning hourly rewards Stake securely and start earning monthly rewards @@ -706,7 +708,7 @@ Block Day Each day - Each ~%1$ssec + Each minute Epoch Era Hour diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt index 0991dcdf5a..6db1972160 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt @@ -39,6 +39,7 @@ import com.tangem.core.ui.res.TangemThemePreview * @param captionColor caption text color * @param isGrayscaleImage whether to display grayscale image * @param iconEndRes icon to end of row + * @param onImageError composable to show if image loading failed * @param extraContent extra content */ @Composable @@ -53,6 +54,7 @@ internal fun InputRowImageBase( iconTint: Color = TangemTheme.colors.icon.informative, isGrayscaleImage: Boolean = false, iconEndRes: Int? = null, + onImageError: (@Composable () -> Unit)? = null, extraContent: (@Composable RowScope.() -> Unit)? = null, ) { Row( @@ -63,13 +65,13 @@ internal fun InputRowImageBase( InputRowAsyncImage( imageUrl = imageUrl, isGrayscale = isGrayscaleImage, + onImageError = onImageError, modifier = Modifier .size(TangemTheme.dimens.spacing36) .clip(TangemTheme.shapes.roundedCornersXLarge), ) SpacerW12() - } - if (iconRes != null) { + } else if (iconRes != null) { Box( contentAlignment = Alignment.Center, modifier = Modifier diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt index 19bb005a00..2905e007b7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt @@ -35,6 +35,7 @@ import com.tangem.core.ui.res.TangemThemePreview * @param captionColor caption text color * @param isGrayscaleImage whether to display grayscale image * @param iconEndRes icon to end of row + * @param onImageError composable to show if image loading failed */ @Suppress("LongParameterList") @Composable @@ -52,6 +53,7 @@ fun InputRowImageInfo( iconTint: Color = TangemTheme.colors.icon.informative, isGrayscaleImage: Boolean = false, @DrawableRes iconEndRes: Int? = null, + onImageError: (@Composable () -> Unit)? = null, ) { Column( verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6), @@ -75,6 +77,7 @@ fun InputRowImageInfo( captionColor = captionColor, isGrayscaleImage = isGrayscaleImage, iconEndRes = iconEndRes, + onImageError = onImageError, ) { Column( verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageSelector.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageSelector.kt index 6fcc6ef997..287edc1197 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageSelector.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageSelector.kt @@ -41,6 +41,7 @@ import java.math.BigDecimal * @param captionColor caption text color * @param isSelected true if selected * @param selectorContent selector content + * @param onImageError composable to show if image loading failed */ @Composable fun InputRowImageSelector( @@ -52,6 +53,7 @@ fun InputRowImageSelector( subtitleColor: Color = TangemTheme.colors.text.primary1, captionColor: Color = TangemTheme.colors.text.tertiary, isSelected: Boolean = false, + onImageError: (@Composable () -> Unit)? = null, selectorContent: @Composable ((isSelected: Boolean, isEnabled: Boolean, onSelect: () -> Unit) -> Unit), ) { InputRowImageBase( @@ -60,6 +62,7 @@ fun InputRowImageSelector( imageUrl = imageUrl, subtitleColor = subtitleColor, captionColor = captionColor, + onImageError = onImageError, modifier = modifier .clickable( onClick = onSelect, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/InputRowAsyncImage.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/InputRowAsyncImage.kt index b85e91ba30..3b46eeffc5 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/InputRowAsyncImage.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/InputRowAsyncImage.kt @@ -18,9 +18,15 @@ import com.tangem.core.ui.utils.getGreyScaleColorFilter * @param imageUrl url of the image * @param modifier modifier * @param isGrayscale whether to apply grayscale filter + * @param onImageError composable to show if image loading failed */ @Composable -internal fun InputRowAsyncImage(imageUrl: String, modifier: Modifier = Modifier, isGrayscale: Boolean = false) { +internal fun InputRowAsyncImage( + imageUrl: String, + modifier: Modifier = Modifier, + isGrayscale: Boolean = false, + onImageError: (@Composable () -> Unit)? = null, +) { val (alpha, colorFilter) = getGreyScaleColorFilter(isGrayscale = isGrayscale) SubcomposeAsyncImage( modifier = modifier, @@ -33,7 +39,7 @@ internal fun InputRowAsyncImage(imageUrl: String, modifier: Modifier = Modifier, .build(), loading = { LoadingIcon() }, error = { - Box( + onImageError?.invoke() ?: Box( modifier = Modifier .background( color = TangemTheme.colors.background.tertiary, diff --git a/core/ui/src/main/res/drawable/ic_staking_filled_18.xml b/core/ui/src/main/res/drawable/ic_staking_filled_18.xml new file mode 100644 index 0000000000..e6741b6156 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_staking_filled_18.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt index f4f4da10b4..181d37a8a2 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt @@ -73,6 +73,7 @@ internal class AddStakingNotificationsTransformer( isSubtractAvailable = isSubtractAvailable, reduceAmountBy = reduceAmountBy, ) + val minimumRequirement = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum.orZero() val sendingAmount = if (isEnterAction) { checkAndCalculateSubtractedAmount( isAmountSubtractAvailable = isSubtractAvailable, @@ -84,7 +85,7 @@ internal class AddStakingNotificationsTransformer( } else { // No amount is taken from account balance on exit or pending actions BigDecimal.ZERO - } + }.max(minimumRequirement) val notifications = buildList { // errors @@ -106,7 +107,7 @@ internal class AddStakingNotificationsTransformer( amountState = amountState, feeState = feeState, sendingAmount = sendingAmount, - isFeeCoverage = isFeeCoverage && isEnterAction, + isFeeCoverage = isFeeCoverage && isEnterAction && !sendingAmount.equals(minimumRequirement), ) addInfoNotifications(prevState) @@ -223,11 +224,10 @@ internal class AddStakingNotificationsTransformer( cryptoCurrencyStatus: CryptoCurrencyStatus, onClick: (CryptoCurrency) -> Unit, ) { - val minimumRequirement = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO if (!isSubtractionAvailable) return - val showNotification = sendingAmount + feeAmount > balance - minimumRequirement.orZero() + val showNotification = sendingAmount + feeAmount > balance if (showNotification) { val notification = if (actionType == StakingActionCommonType.ENTER) { NotificationUM.Error.TotalExceedsBalance diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt index bd88fa6ffc..27d5d30086 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt @@ -35,7 +35,7 @@ internal class SetConfirmationStateLoadingTransformer( availableValidators = yield.validators, ), notifications = persistentListOf(), - footerText = getFooter(prevState), + footerText = getFooter(prevState, chosenValidator), transactionDoneState = TransactionDoneState.Empty, pendingAction = possibleConfirmationState?.pendingAction, pendingActions = possibleConfirmationState?.pendingActions, @@ -45,14 +45,12 @@ internal class SetConfirmationStateLoadingTransformer( ) } - private fun getFooter(state: StakingUiState): TextReference { + private fun getFooter(state: StakingUiState, validator: Yield.Validator): TextReference { val amountState = state.amountState as? AmountState.Data - val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data - val validatorState = confirmationState?.validatorState as? ValidatorState.Content val isEnterAction = state.actionType == StakingActionCommonType.ENTER - val apr = validatorState?.chosenValidator?.apr.orZero() + val apr = validator.apr.orZero() val amountDecimal = amountState?.amountTextField?.fiatAmount?.value val potentialReward = amountDecimal?.multiply(apr) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index 9ed92bf14b..517cbba094 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -258,10 +258,7 @@ internal class SetInitialDataStateTransformer( Yield.Metadata.RewardSchedule.BLOCK -> { val networkId = cryptoCurrencyStatusProvider().currency.network.id.value when { - isCosmos(networkId) -> resourceReference( - R.string.staking_reward_schedule_each_sec, - wrappedList(COSMOS_BLOCK_TIME), - ) + isCosmos(networkId) -> resourceReference(R.string.staking_reward_schedule_each_minute) else -> resourceReference(R.string.staking_reward_schedule_each_day) } } @@ -278,8 +275,6 @@ internal class SetInitialDataStateTransformer( private companion object { val EQUALITY_THRESHOLD = BigDecimal(1E-10) - const val COSMOS_BLOCK_TIME = "20" // 20 seconds - val rewardClaimingResources = mapOf( Yield.Metadata.RewardClaiming.MANUAL to R.string.staking_reward_claiming_manual, Yield.Metadata.RewardClaiming.AUTO to R.string.staking_reward_claiming_auto, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt index cbcb806d97..fecd9f9cb6 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt @@ -52,6 +52,7 @@ internal fun StakingClaimRewardsValidatorContent( infoTitle = item.fiatAmount, infoSubtitle = item.cryptoAmount, imageUrl = item.validator?.image.orEmpty(), + onImageError = { ValidatorImagePlaceholder() }, modifier = modifier .roundedShapeItemDecoration(index, state.rewards.lastIndex, false) .background(TangemTheme.colors.background.action) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index 6fa945af4f..679dd0675d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -57,7 +57,6 @@ import com.tangem.features.staking.impl.presentation.state.previewdata.InitialSt import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents import com.tangem.utils.StringsSigns.DOT -import com.tangem.utils.StringsSigns.PLUS import com.tangem.utils.extensions.orZero private const val BANNER_BLOCK_KEY = "BannerBlock" @@ -231,8 +230,6 @@ private fun StakingRewardBlock( val (text, textColor) = when (rewardBlockType) { RewardBlockType.Rewards -> { annotatedReference { - append(PLUS) - appendSpace() append(rewardFiat.orMaskWithStars(isBalanceHidden)) appendSpace() append(DOT) @@ -282,6 +279,7 @@ private fun ActiveStakingBlock( imageUrl = balance.getImage(), iconRes = icon, iconTint = iconTint, + onImageError = { ValidatorImagePlaceholder() }, modifier = modifier .background(TangemTheme.colors.background.action) .clickable( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt index 9f8db40d8e..c86ebb7e95 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt @@ -3,10 +3,7 @@ package com.tangem.features.staking.impl.presentation.ui import android.content.res.Configuration import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Icon import androidx.compose.runtime.Composable @@ -95,6 +92,7 @@ internal fun StakingValidatorListContent( ) } }, + onImageError = { ValidatorImagePlaceholder() }, ) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/ValidatorImagePlaceholder.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/ValidatorImagePlaceholder.kt new file mode 100644 index 0000000000..d935c0e9ff --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/ValidatorImagePlaceholder.kt @@ -0,0 +1,25 @@ +package com.tangem.features.staking.impl.presentation.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.vectorResource +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.staking.impl.R + +@Composable +internal fun ValidatorImagePlaceholder() { + Icon( + painter = rememberVectorPainter(ImageVector.vectorResource(R.drawable.ic_staking_filled_18)), + contentDescription = null, + tint = TangemTheme.colors.icon.inactive, + modifier = Modifier + .background(TangemTheme.colors.icon.primary1, CircleShape) + .padding(TangemTheme.dimens.size9), + ) +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt index be7909007e..e895963463 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt @@ -16,6 +16,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.ValidatorState +import com.tangem.features.staking.impl.presentation.ui.ValidatorImagePlaceholder import com.tangem.utils.extensions.orZero @Composable @@ -45,6 +46,7 @@ internal fun ValidatorBlock(validatorState: ValidatorState, onClick: () -> Unit) ) }, imageUrl = validatorState.chosenValidator.image.orEmpty(), + onImageError = { ValidatorImagePlaceholder() }, ) } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 190a186493..dafdd53e8c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -21,7 +21,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.* import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsTxHistoryTransactionStateConverter import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getBalance -import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getStringResourceId import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.features.tokendetails.impl.R @@ -204,7 +203,7 @@ internal class TokenDetailsLoadedBalanceConverter( formatArgs = wrappedList(apr), ), subtitleText = resourceReference( - id = stakingEntryInfo.rewardSchedule.getStringResourceId(), + id = R.string.staking_notification_earn_rewards_text, formatArgs = wrappedList(stakingEntryInfo.tokenSymbol), ), iconState = iconState, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenStakingStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenStakingStateConverter.kt index b12e12eb53..14bd84bf23 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenStakingStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenStakingStateConverter.kt @@ -8,7 +8,6 @@ import com.tangem.domain.staking.model.StakingEntryInfo import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState -import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getStringResourceId import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider @@ -39,7 +38,7 @@ internal class TokenStakingStateConverter( formatArgs = wrappedList(apr), ), subtitleText = resourceReference( - id = stakingEntryInfo.rewardSchedule.getStringResourceId(), + id = R.string.staking_notification_earn_rewards_text, formatArgs = wrappedList(stakingEntryInfo.tokenSymbol), ), iconState = iconState, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/utils/RewardScheduleUtils.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/utils/RewardScheduleUtils.kt deleted file mode 100644 index 1219166e7b..0000000000 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/utils/RewardScheduleUtils.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.state.utils - -import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.features.tokendetails.impl.R - -internal fun Yield.Metadata.RewardSchedule.getStringResourceId(): Int { - return when (this) { - Yield.Metadata.RewardSchedule.BLOCK, - Yield.Metadata.RewardSchedule.DAY, - Yield.Metadata.RewardSchedule.ERA, - Yield.Metadata.RewardSchedule.EPOCH, - -> R.string.staking_notification_earn_rewards_text_daily - - Yield.Metadata.RewardSchedule.HOUR, - -> R.string.staking_notification_earn_rewards_text_hourly - - Yield.Metadata.RewardSchedule.WEEK, - -> R.string.staking_notification_earn_rewards_text_weekly - - Yield.Metadata.RewardSchedule.MONTH, - -> R.string.staking_notification_earn_rewards_text_monthly - - else - -> R.string.staking_notification_earn_rewards_text_daily - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index a42d33d598..d8b674e7df 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.geometry.* import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path @@ -386,11 +387,13 @@ private inline fun BaseScaffoldWithMarkets( LocalMainBottomSheetColor provides remember { mutableStateOf(backgroundPrimary) }, ) { val backgroundColor = LocalMainBottomSheetColor.current + var isSearchFieldFocused by remember { mutableStateOf(false) } BottomSheetStateEffects( bottomSheetState = bottomSheetState, alertConfig = alertConfig, onBottomSheetStateChange = onBottomSheetStateChange, + isSearchFieldFocused = isSearchFieldFocused, ) TangemBottomSheetScaffold( @@ -422,7 +425,14 @@ private inline fun BaseScaffoldWithMarkets( modifier = Modifier.sizeIn(maxHeight = maxHeight - statusBarHeight), ) { Hand(Modifier.drawBehind { drawRect(backgroundColor.value) }) - bottomSheetContent() + Box( + modifier = Modifier + .onFocusChanged { + isSearchFieldFocused = it.isFocused + }, + ) { + bottomSheetContent() + } } }, content = { paddingValues -> @@ -463,6 +473,7 @@ private inline fun BaseScaffoldWithMarkets( state.showMarketsOnboarding, onDismissRequest = { coroutineScope.launch { bottomSheetState.partialExpand() } + state.onDismissMarketsOnboarding() }, ) @@ -640,6 +651,7 @@ private fun BottomSheetStateEffects( bottomSheetState: TangemSheetState, alertConfig: WalletAlertState?, onBottomSheetStateChange: (BottomSheetState) -> Unit, + isSearchFieldFocused: Boolean, ) { val systemUiController = rememberSystemUiController() val navigationBarColor = TangemTheme.colors.background.primary @@ -688,7 +700,7 @@ private fun BottomSheetStateEffects( val isKeyboardVisible by rememberIsKeyboardVisible() LaunchedEffect(isKeyboardVisible) { - if (isKeyboardVisible && alertConfig == null) { + if (isKeyboardVisible && alertConfig == null && isSearchFieldFocused) { bottomSheetState.expand() } } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index cb4e3722a1..7e7f4a8b81 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -89,7 +89,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.15-778" +tangemBlockchainSdk = "release-app_5.15-780" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.15-382" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 93d05ff8156bb6f36c11a8ea7ff28bd4f0c7925f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 19 Sep 2024 21:08:35 +0500 Subject: [PATCH 02/18] Updated on 2026-08-14 --- .../drawable/ill_pastel_cards3_120_106.webp | Bin 9576 -> 9308 bytes .../drawable/ill_vivid_cards3_120_106.webp | Bin 9308 -> 9576 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/features/wallet/impl/src/main/res/drawable/ill_pastel_cards3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_pastel_cards3_120_106.webp index 1b024df94de5d539f962e9077dce76848b7977dc..ecc36428eee22c32d47271fa206c529535f5db33 100644 GIT binary patch literal 9308 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 K%kM?BfB*n+-b$tb literal 9576 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 K%kM?BfB*n+-b$tb From 21912f4c7f6b4cc327078416491e125eba2fcb49 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 12:23:04 +0300 Subject: [PATCH 03/18] Updated on 2026-08-14 --- .../domain/tokens/operations/CurrencyStatusOperations.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt index c8990cebf4..abdf3f94fd 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt @@ -61,7 +61,7 @@ internal class CurrencyStatusOperations( (yieldBalance as? YieldBalance.Data)?.address == status.address.defaultAddress.value val currentYieldBalance = yieldBalance.takeIf { isCurrentAddressStaking } return when { - ignoreQuote || quote == null -> CryptoCurrencyStatus.NoQuote( + ignoreQuote -> CryptoCurrencyStatus.NoQuote( amount = amount, hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, pendingTransactions = currentTransactions, @@ -70,14 +70,15 @@ internal class CurrencyStatusOperations( ) currency is CryptoCurrency.Token && currency.isCustom -> CryptoCurrencyStatus.Custom( amount = amount, - fiatAmount = calculateFiatAmountOrNull(amount, quote.fiatRate), - fiatRate = quote.fiatRate, - priceChange = quote.priceChange, + fiatAmount = calculateFiatAmountOrNull(amount, quote?.fiatRate), + fiatRate = quote?.fiatRate, + priceChange = quote?.priceChange, hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, pendingTransactions = currentTransactions, networkAddress = status.address, yieldBalance = currentYieldBalance, ) + quote == null -> CryptoCurrencyStatus.Loading else -> CryptoCurrencyStatus.Loaded( amount = amount, fiatAmount = calculateFiatAmount(amount, quote.fiatRate), From badfcf2059a5625f0bda486df54713680a78c47b Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 13:07:15 +0300 Subject: [PATCH 04/18] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 29 ++++++++---- core/res/src/main/res/values-es/strings.xml | 41 +++++++++++++++-- core/res/src/main/res/values-fr/strings.xml | 45 ++++++++++++++++--- core/res/src/main/res/values-ja/strings.xml | 12 +++++ core/res/src/main/res/values-ru/strings.xml | 18 ++++++-- .../src/main/res/values-uk-rUA/strings.xml | 23 +++++++--- core/res/src/main/res/values/strings.xml | 17 +++++-- 7 files changed, 157 insertions(+), 28 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index a72160333d..177bb4faf9 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -39,7 +39,7 @@ Zu viele Versuche Du hast die biometrische Authentifizierung auf deinem Telefon deaktiviert und kannst keine Wallets in der App speichern. Um Wallets zu speichern, aktiviere bitte die biometrische Authentifizierung in deinen Telefoneinstellungen. Backup-Prozess starten - Von deiner Fiat-Karte oder deinem Bankkonto + Verwende eine Bankkarte oder eine andere Zahlungsmethode %d Karte %d Karten @@ -275,7 +275,7 @@ Keine Token gefunden. Bitte versuche eine andere Anfrage ID: %s Transaktions-ID kopiert - Aus einer anderen Währung in deiner Wallet + Wechsel eine Ihrer Währungen in eine andere Die folgenden Angaben sind freiwillig. Du kannst diese löschen, wenn du sie nicht weitergeben möchtest. Teile uns mit, welche Funktionen du vermisst, und wir werden versuchen, dir zu helfen. Bitte sag uns, welche Karte du hast @@ -356,6 +356,7 @@ Die Daten konnten nicht geladen werden... Keine Daten Schnelle Aktionen + Markt durchsuchen Ergebnis Token unter 100k USD Marktkapitalisierung anzeigen Token anzeigen @@ -386,25 +387,32 @@ Kaufdruck Umlaufmenge Die Gesamtzahl der Coins, die für den Handel verfügbar sind und auf dem Markt zirkulieren + Umlaufmenge Erfahrene Käufer Nettokäufer mit der zusätzlichen Anforderung, mindestens 100 ausgehende Transaktionen zu haben + Erfahrene Käufer Vollständig verwässerte Bewertung Der theoretische Gesamtwert einer Kryptowährung, wenn alle Coins, die existieren könnten, im Umlauf sind, einschließlich derjenigen, die derzeit nicht im Umlauf sind + Vollständig verwässerte Bewertung Entstehungsdatum Hoch Inhaber/ Halter Die Änderung der Anzahl der Token-Inhaber innerhalb eines bestimmten Zeitraums + Inhaber/ Halter Einblicke Links Liquidität Die Änderung der Liquidität, die dem Token während des angegebenen Zeitraums zur Verfügung steht + Liquidität Liquiditätsindex Leer Niedrig MarketCap - Der Gesamtmarktwert einer Kryptowährung, berechnet durch Multiplikation des aktuellen Preises des Coins mit der Gesamtzahl der im Umlauf befindlichen Coins. + Der Gesamtmarktwert einer Kryptowährung, berechnet sich durch Multiplikation des aktuellen Preises des Coins mit der Gesamtzahl der im Umlauf befindlichen Coins + MarketCap Marktbewertung Position im Krypto-Rating zwischen allen Coins basierend auf der Marktkapitalisierung + Marktbewertung Maximale Versorgung Leer Metriken @@ -415,8 +423,10 @@ Soziales Gesamtangebot Die maximale Anzahl von Coins oder Tokens, die jemals für eine bestimmte Kryptowährung existieren können + Gesamtangebot Handelsvolumen (24h) Der Gesamtbetrag einer Kryptowährung, der innerhalb der letzten 24 Stunden gehandelt wurde, wobei das Aktivitäts- und Liquiditätsniveau auf dem Markt angegeben wird + Handelsvolumen (24h) Rufe dies auf oder tippe auf die Suchleiste, um Token direkt vom Markt hinzuzufügen Token hinzufügen NFC ist auf deinem Gerät nicht verfügbar @@ -520,7 +530,7 @@ Kamerazugriff verweigert %1$s ( %2$s ) im %3$s Netzwerk Sende nur %s an diese Adresse. Der Versand einer anderen Währung führt zu ihrem unwiderruflichen Verlust. - QR-Code anzeigen oder Adresse teilen + Überweise Geld von einem anderen Wallet oder einer anderen Börse Teilnehmen Die Informationen zum Empfehlungsprogramm konnten nicht geladen werden. Bitte versuche es später noch einmal. Die Informationen über das Empfehlungsprogramm konnten nicht geladen werden. Fehlercode: %s. Bitte versuche es später noch einmal. @@ -605,7 +615,7 @@ Gesamtbetrag übersteigt den Saldo Ein Guthaben von mindestens %s ist erforderlich, um dein Konto in der Blockchain aktiv zu halten und Sicherheitsrisiken zu vermeiden. Dieser Betrag verbleibt auf deinem Guthaben und kann nicht abgehoben werden. Mindesteinlage - Der Kommissionsbetrag ist %s mal der empfohlene Betrag. Stelle sicher, dass die benutzerdefinierten Einstellungen korrekt sind. + Dein Provisionsbetrag ist %s -mal höher als der empfohlene Betrag. Bitte überprüfe benutzerdefinierten Einstellungen und pass diese an. Die individuelle Gebühr ist hoch Aufgrund der Besonderheiten des Netzes %1$s ist die Gebühr für die Überweisung des gesamten Guthabens höher. Um die Kommission zu reduzieren, Kannst du %2$s verlassen. Die Gebühr ist höher @@ -639,7 +649,7 @@ Du sendest **%1$s** und %2$s Du sendest **%1$s** Die Netzwerkgebühr wird durch die Nutzung von %1$s Energieträgern gedeckt. - Die Netzwerkgebühr wird durch das Ausgeben von %1$s Energieträgern reduziert. + Die Netzwerkgebühr wird durch den Verbrauch von %1$s Energie reduziert inklusive einer Netzgebühr von %1$s Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert %1$s ist ein Vermögenswert im Tron-Netzwerk. Um die Gebühr zu berechnen und eine Transaktion durchzuführen, musst du etwas Tron (TRX) auf deinem Konto einzahlen. @@ -659,7 +669,7 @@ Belohnungen sammeln sich täglich automatisch in deinem Staking-Konto an. Verfügbar Durchschnittliche Belohnungsquote - Was sit Staking? + Was ist Staking? %s geschätzter Profit Marktbewertung Metriken @@ -679,7 +689,8 @@ Gesperrt Migrieren Natives Staking - Verdiente Belohnungen werden an deine Wallet gesendet und stehen sofort zur Verwendung zur Verfügung + Verdiente Belohnungen werden an Deine Wallet gesendet und stehen Dir sofort zur Verfügung + Stake sicher und verdienen Belohnungen Sicher staken und tägliche Belohnungen verdienen. Sicher staken und stündliche Belohnungen verdienen. Sicher staken und monatliche Belohnungen verdienen. @@ -708,6 +719,8 @@ Block Tag Täglich + Minütlich + Alle ~ %1$s Sek. Epoche Ära Stunde diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 0bfcf8de3d..3116f0a193 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -39,7 +39,7 @@ Demasiados intentos Ha desactivado la autenticación biométrica en su teléfono y no podrá guardar billeteras en la aplicación. Para guardar billeteras, active la función de autenticación biométrica en los ajustes de su teléfono. Iniciar proceso de backup - Con su tarjeta bancaria o cuenta bancaria + Utilice una tarjeta bancaria u otros métodos de pago %d tarjeta %d tarjetas @@ -84,6 +84,7 @@ No ha otorgado acceso a su cámara, cambie su configuración de privacidad Cancelar Elige una acción + Reclamar Reclame recompensas Cerrar Continuar @@ -274,7 +275,7 @@ No se encontraron fichas. Por favor intenta con otra solicitud ID : %s ID de transacción copiado - Con otra moneda en su billetera + Convierta una de sus monedas a otra La siguiente información es opcional. Puede borrarla si no quiere compartirla. Cuéntenos qué funciones echa de menos y trataremos de ayudarle. Por favor, díganos qué tarjeta tiene @@ -355,6 +356,7 @@ No se pueden cargar los datos… Sin datos Acciones rápidas + Busque en el mercado Resultado Ver tokens con una capitalización de mercado de menos de $100,000 Mostrar tokens @@ -385,24 +387,31 @@ Presión de compra Suministro circulante El número total de monedas que están disponibles para el comercio y que circulan en el mercado. + Suministro circulante Compradores experimentados Compradores netos con el requisito adicional de tener al menos 100 transacciones salientes + Compradores experimentados Valoración totalmente diluida El valor teórico total de una criptomoneda si todas las monedas que podrían existir estuvieran en circulación, incluidas las que actualmente no circulan. + Valoración totalmente diluida Dato de Genesis Alto Titulares El cambio en el número de poseedores de tokens dentro de un período de tiempo específico + Titulares Ideas Enlaces Liquidez El cambio en la cantidad de liquidez disponible para el token durante el período de tiempo especificado + Liquidez Índice de liquidez Bajo Capital. de mercado El valor total de una criptomoneda calculado multiplicando su precio por la cantidad de monedas en circulación + Capital. de mercado Evaluación de mercado Posición en la clasificación de criptomonedas entre todas las monedas según la capitalización de mercado + Evaluación de mercado Suministro máximo Métrica Enlaces oficiales @@ -412,8 +421,10 @@ Social Suministro total La cantidad máxima de monedas o tokens que pueden existir para una criptomoneda en particular + Suministro total Volumen de operaciones (24 horas) La cantidad total de una criptomoneda que se ha negociado en las últimas 24 horas, lo que indica el nivel de actividad y liquidez en el mercado. + Volumen de operaciones (24 horas) Tire hacia arriba o toque la barra de búsqueda para agregar tokens directamente desde el mercado Agregar tokens NFC no está disponible en su dispositivo @@ -517,7 +528,7 @@ Acceso a la cámara denegado %1$s (%2$s) en la red %3$s Envíe solo %s a esta dirección. Enviar cualquier otra moneda resultará en su pérdida irreversible. - Muestra un código QR o comparte tu dirección + Transfiera fondos desde otra billetera o intercambio Participar Error al cargar la información sobre el programa de referidos. Por favor, inténtelo de nuevo más tarde. Error al cargar la información sobre el programa de referidos. Código de error: %s. Por favor, inténtelo de nuevo más tarde. @@ -634,6 +645,10 @@ Enviar %s Está enviando **%1$s** incluida una tarifa de red de %2$s Está enviando**%1$s** y %2$s + Está enviando **%1$s** + La tarifa de red se cubrirá al usar %1$s energía + La tarifa de red se reducirá al usar %1$s energía + incluida una tarifa de red de %1$s La transacción se firmó con éxito y se envió al nodo blockchain. El saldo de la billetera se actualizará después de un tiempo. %1$s es un activo en la red Tron. Para calcular la tarifa y realizar una transacción, deba depositar algo de Tron (TRX) en su cuenta. Transacción enviada @@ -644,6 +659,8 @@ El monto del staking debe ser al menos %s El monto del staking se redondeará a %1$s TRX debido a las reglas de la red. Unstaking de la reclamación + Tarifa de staking de la cuenta + Una cuenta de staking es una cuenta especial donde se almacenan los tokens SOL de staking. Se crea cuando delegas sus tokens a un validador para participar en la validación de transacciones y ganar recompensas. Se cobra una pequeña tarifa por crear la cuenta de staking, que se devuelve una vez que se completa el staking. Porcentaje de rendimiento anual El porcentaje de rendimiento anual que podrá ganar como participante en el staking. APR @@ -671,14 +688,25 @@ Migrar Native staking Recomp. won se le enviará y estará disponible para su uso inmediato + Haga staking de forma segura y comience a ganar recompensas + Haga de forma segura y comience a ganar recompensas diarias + Haga staking de forma segura y comience a ganar recompensas por hora + Haga staking de forma segura y comience a ganar recompensas mensuales El staking le permite ganar %1$s. Sus recompensas por apostar llegan todos los días. El staking le permite ganar %1$s. Sus recompensas del staking llegan cada hora. El staking le permite ganar %1$s. Sus recompensas del staking llegan todos los meses. El staking le permite ganar %1$s. Sus recompensas por apostar llegan todas las semanas. + Haga staking de forma segura y comience a ganar recompensas semanales Gane recompensas por staking + El staking no está disponible actualmente debido a las condiciones de la red. Inténtelo de nuevo más tarde. + El staking en la red %1$s con un nuevo validador transferirá automáticamente todos los fondos previamente en staking a este validador. + Reinvierta las recompensas obtenidas en el monto apostado, aumentando las ganancias potenciales. + Desbloquee su dinero para retirarlo del proceso de staking. El desbloqueo demora %s minuto. + Sus fondos estarán disponibles para su uso después del período de desvinculación de 21 días. La recompensa se retirará junto con los fondos de desvinculación. Sus fondos estarán disponibles para su uso después del período de desvinculación de %s. Ya puede retirar sus fondos, estarán disponibles para usar de inmediato. Hacer staking en la red Tron con un nuevo validador transferirá automáticamente todos los fondos de staking a este validador. + Preparando Listo para retirar Reunir Haga el staking de nuevo @@ -690,6 +718,8 @@ Bloquear Día Cada día + Cada minuto + Cada ~%1$s segundo(s) Época Era Hora @@ -698,6 +728,7 @@ Recompensas El stake está bloqueado Hacer más staking + Haga staking de %1$s y recibirá %2$s anualmente Toque para desbloquear Toque para retirar Stake %s @@ -705,8 +736,10 @@ ¡La transacción se está procesando! La validación está en curso en la cadena de bloques. Esto puede tardar unos minutos. Desunión Desbloquear + Desbloqueando Sin staking Unstaking + Detenando el staking de activos %s Validador Validadores Votar @@ -846,6 +879,8 @@ Error de activación Según los desarrolladores de la red BNB, el soporte para el estándar BEP-2\nfinalizará en junio de 2024. Para evitar perder activos con este estándar, por favor conviértalos al estándar BEP-20. Usa nuestro servicio de swap para transferirlos a la red BNB Smart Chain. BNB Beacon Chain se cerrará + Por favor deposite %1$s para cubrir la tarifa de red + Fondos insuficientes para cubrir la tarifa de red Podría ser mejor Me gusta Entendido diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 0d3cb6b2bc..2a75d83b48 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -39,7 +39,7 @@ Trop de tentatives Vous avez désactivé l\'authentification biométrique sur votre téléphone et ne pourrez pas enregistrer de portefeuilles dans l\'application. Pour enregistrer des portefeuilles, veuillez activer la fonction d\'authentification biométrique dans les paramètres de votre téléphone. Démarrer le processus de sauvegarde - Avec votre carte bancaire ou votre compte bancaire + Utilisez une carte bancaire ou d\'autres moyens de paiement %d carte %d cartes @@ -84,6 +84,7 @@ Vous n\'avez pas octroyé l\'accès à votre caméra, veuillez modifier vos paramètres de confidentialité Annuler Choisissez une action + Réclamer Réclamez des récompenses Fermer Continuer @@ -274,7 +275,7 @@ Aucun jeton trouvé. Veuillez essayer une autre demande ID : %s ID de transaction copié - Avec une autre devise dans votre portefeuille + Convertissez une de vos devises en une autre Les informations suivantes sont facultatives. Vous pouvez les effacer si vous ne souhaitez pas les partager. Dites-nous quelles fonctions vous manquent, et nous essaierons de vous aider. Veuillez nous dire quelle carte vous avez @@ -355,6 +356,7 @@ Impossible de charger les données… Aucune donnée Actions rapides + Rechercher sur le marché Résultat Voir les jetons de moins de 100 000 $ de capitalisation boursière Afficher les jetons @@ -385,24 +387,31 @@ Pression d\'achat Approvisionnement en circulation Le nombre total de pièces disponibles pour le trading et en circulation sur le marché + Approvisionnement en circulation Acheteurs expérimentés Acheteurs nets avec l\'exigence supplémentaire d\'avoir au moins 100 transactions sortantes + Acheteurs expérimentés Valorisation entièrement diluée La valeur théorique totale d\'une crypto-monnaie si toutes les pièces qui pourraient exister étaient en circulation, y compris celles qui ne circulent pas actuellement + Valorisation entièrement diluée Date de la Genesis Haut Détenteurs L\'évolution du nombre de détenteurs de jetons au cours d\'une période donnée + Détenteurs Idées Liens Liquidité Le changement dans la quantité de liquidité disponible pour le jeton pendant la période spécifiée + Liquidité Indice de liquidité Faible Cap. boursière La valeur d\'une crypto-monnaie est calculée en multipliant son prix par le nombre de pièces en circulation + Cap. boursière Évaluation du marché Position dans le classement des crypto-monnaies entre toutes les pièces en fonction de la capitalisation boursière + Évaluation du marché Approvisionnement maximal Métriques Official links @@ -412,8 +421,10 @@ Social Approvisionnement total Le nombre maximal de pièces ou de jetons pouvant exister pour une crypto-monnaie particulière + Approvisionnement total Volume des échanges (24h) Le montant total d\'une crypto-monnaie qui a été échangé au cours des dernières 24 heures, indiquant le niveau d\'activité et de liquidité du marché + Volume des échanges (24h) Tirez vers le haut ou appuyez sur la barre de recherche pour ajouter des jetons directement depuis le marché Ajouter des jetons NFC n\'est pas disponible sur votre appareil @@ -460,12 +471,12 @@ Sauvegarde en cours En savoir plus sur les seed phrases - + Vide Écrivez ces %d mots dans l\'ordre indiqué ci-dessous et conservez-les dans un endroit sûr et secret. Votre seed phrase - + Vide %d mots Pour importer votre portefeuille, entrez votre seed phrase dans le champ ci-dessous @@ -517,7 +528,7 @@ Accès à la caméra refusé %1$s (%2$s) sur le réseau %3$s Envoyez uniquement %s à cette adresse. L\'envoi de toute autre devise entraînera sa perte irréversible. - Affichez un code QR ou partagez votre adresse + Transférez des fonds depuis un autre portefeuille ou une autre bourse Participer Échec du chargement des informations sur le programme de parrainage. Veuillez réessayer plus tard. Échec du chargement des informations sur le programme de parrainage. Code d\'erreur : %s. Veuillez réessayer plus tard. @@ -634,6 +645,10 @@ Envoyer %s Vous envoyez **%1$s** incluant des frais de réseau de %2$s Vous envoyez **%1$s** et %2$s + Vous envoyez **%1$s** + les frais de réseau seront couverts en utilisant %1$s énergie + les frais de réseau seront réduits en utilisant %1$s énergie + y compris des frais de réseau de %1$s La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps %1$s est un actif du réseau Tron. Pour calculer les frais et effectuer une transaction, déposez du Tron (TRX) sur votre compte. Transaction envoyée @@ -644,6 +659,8 @@ Le montant à staker doit être au moins %s Le montant du staking sera arrondi à %1$s TRX en raison des règles du réseau. Réclamation déstakée + Frais de staking du compte + Un compte de staking est un compte spécial où sont stockés les jetons SOL stakés. Il est créé lorsque vous déléguez vos jetons à un validateur pour participer à la validation des transactions et gagner des récompenses. Des frais minimes sont facturés pour la création du compte de staking, qui sont restitués une fois le staking terminé. Pourcentage de rendement annuel Le pourcentage de rendement annuel que vous pouvez gagner en participant au staking. APR @@ -671,14 +688,25 @@ Migrer Native staking Récomp. gagnées vous seront envoyées et dispo pour utilisation immédiate + Stakez et commencez à gagner vos récompenses + Stakez en toute sécurité et commencez à gagner des récompenses quotidiennes + Stakez en toute sécurité et commencez à gagner des récompenses horaires + Stakez en toute sécurité et commencez à gagner des récompenses mensuelles Le staking vous permet de gagner %1$s. Vos récompenses de staking arrivent tous les jours. Le staking vous permet de gagner %1$s. Vos récompenses de staking arrivent toutes les heures. Le staking vous permet de gagner %1$s. Vos récompenses de staking arrivent tous les mois. Le staking vous permet de gagner %1$s. Vos récompenses de staking arrivent toutes les semaines. + Stakez en toute sécurité et commencez à gagner des récompenses hebdomadaires Gagnez des récompenses de staking + L\'option de staking n\'est actuellement pas disponible en raison des conditions du réseau. Veuillez réessayer plus tard. + Le staking dans le réseau %1$s avec un nouveau validateur transférera automatiquement tous les fonds précédemment stakés vers ce validateur + Réinvestissez vos récompenses gagnées dans le montant que vous avez staké, augmentant ainsi vos gains potentiels. + Débloquez votre argent pour le retirer du processus de staking. Le déverrouillage prend %s. + Vos fonds seront disponibles à l\'utilisation après la période de déblocage de 21 jours. La récompense sera retirée en même temps que vos fonds de déblocage. Vos fonds seront disponibles pour utilisation après la période de désengagement %s. Vous pouvez désormais retirer vos fonds, ils seront disponibles à l\'utilisation immédiatement Le staking dans le réseau Tron avec un nouveau validateur transférera automatiquement tous les fonds précédemment stakés vers ce validateur + En cours de préparation Prêt à retirer Réassocier Restakez @@ -690,6 +718,8 @@ Bloquer Jour Chaque jour + Chaque minute + Toute(s) les ~%1$sseconde(s) Époque Ère Heure @@ -698,6 +728,7 @@ Récompenses Stake verrouillé Staker plus + Vous stakez %1$s et recevrez %2$s par an Appuyez pour déverrouiller Appuyez pour retirer Stake %s @@ -705,8 +736,10 @@ La transaction est en cours de traitement ! La validation est actuellement en cours dans la blockchain. Cela peut prendre quelques minutes. Dissociation Débloquer + Déverrouillage Non-staké Unstaking + En train de destaker les actifs %s Validateur Validateurs Voter @@ -846,6 +879,8 @@ Erreur d\'activation Selon les développeurs du réseau BNB, le support de la norme BEP-2\nprendra fin en juin 2024. Pour éviter de perdre des actifs avec cette norme, veuillez les convertir à la norme BEP-20. Utilisez notre service de d\'échange pour les transférer sur le réseau BNB Smart Chain. BNB Beacon Chain va s\'arrêter de fonctionner + Veuillez déposer environ %1$s pour couvrir les frais de réseau + Fonds insuffisants pour couvrir les frais de réseau Pas terrible J\'aime Ok, compris! diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index cff94ccc70..7f80905de9 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -383,24 +383,31 @@ 買い圧力 循環供給量 取引可能で市場に流通しているコインの総数 + 循環供給量 経験豊富な買い手 少なくとも100の発信取引を持つネット・バイヤー + 経験豊富な買い手 完全希薄化後評価額 現在流通していないものも含め、存在する可能性のあるすべてのコインが流通している場合の暗号資産の理論上の合計価値 + 完全希薄化後評価額 ジェネシスの日付 高い ホルダー 特定の期間内のトークンホルダー数の変化 + ホルダー インサイト リンク 流動性 指定された期間中のトークンに利用可能な流動性の変化 + 流動性 流動性指数 低い 時価総額 暗号資産の市場価値の合計。コインの現在の価格と、流通しているコインの総数を掛けて計算されます。 + 時価総額 市場格付け 時価総額に基づくすべてのコイン間の暗号資産評価における位置 + 市場格付け 最大供給量 指標 公式リンク @@ -410,8 +417,10 @@ ソーシャル 総供給量 特定の暗号資産に存在しうるコインまたはトークンの最大数 + 総供給量 取引量(24時間) 過去24時間以内に取引された暗号資産の合計額。市場の活発さと流動性を示します。 + 取引量(24時間) これをドラッグするか、検索窓をタップして、マーケットから直接トークンを追加します トークンを追加 お使いのデバイスではNFCが使用できません @@ -671,6 +680,7 @@ 移行 ネイティブステーキング 獲得した報酬はあなたのアドレスに直接送られ、すぐ使用可能です。 + 安全にステーキングして報酬を獲得しましょう 安全にステーキングして、報酬を毎日獲得しましょう 安全にステーキングして、報酬を毎時間獲得しましょう 安全にステーキングして、報酬を毎月獲得しましょう @@ -699,6 +709,8 @@ ブロック 毎日 + 毎分 + %1$s毎 エポック 時代 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index fe50c2b14d..48a4894142 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -276,7 +276,7 @@ Токены не найдены. Пожалуйста, попробуйте другой запрос ID: %s ID транзакции скопирован - Конвертируйте одну из ваших валют в другую + Обменяйте любой актив в вашем портфеле на этот токен Информация ниже не является обязательной. Вы можете стереть её, если хотите. Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. Скажите, пожалуйста, какая у вас карта? @@ -344,7 +344,7 @@ Голосовать Выберите кошелек Кошелёк не поддерживает более одной сети - Чтобы купить, обменять или получить данный токен вам нужно добавить его к себе в портфель + Чтобы купить, обменять или получить данный токен, вам нужно добавить его к себе в портфель Этот актив недоступен Добавить в портфель Добавить @@ -387,27 +387,34 @@ Веб-сайт Покуп. предпоч. Разница между объемом покупателей и продавцов - Покуп. предпоч. + Покупательское предпочтение Циркулир. предл. Общее количество монет, доступных для торговли и находящихся в обращении на рынке + Циркулирующее предложение Опытные трейдеры Покупатели, у которых было как минимум 100 исходящих транзакций. + Опытные трейдеры Полн. разб. кап. Общая теоретическая стоимость криптовалюты, если все монеты, которые могут существовать, находятся в обращении, включая те, которые в настоящее время не обращаются + Полностью разбавленная капитализация Дата создания Высокий Держатели Изменение количества держателей токенов в течение выбранного периода времени + Держатели Инсайты Ссылки Ликвидность Изменение объема ликвидности, доступной для токена в течение указанного периода времени. + Ликвидность Индекс ликвидности Низкий Рын. кап. Общая рыночная стоимость криптовалюты, рассчитываемая путем умножения текущей цены монеты на общее количество монет в обращении. + Рыночная капитализация Рейтинг Позиция в рейтинге криптовалют среди всех монет на основе рыночной капитализации. + Рейтинг Максимальный объем Метрики Официальные ссылки @@ -415,10 +422,12 @@ Репозиторий Оценка безопасности Социальные - Общее предложение + Общ. предл. Максимальное количество монет или токенов, которое может когда-либо существовать для выбранной криптовалюты. + Общее предложение Объем торгов (24ч) Общая сумма криптовалюты, которая была продана за последние 24 часа, показывающая уровень активности и ликвидности на рынке. + Объем торгов (24ч) Потяните вверх или коснитесь поисковой строки, чтобы добавить токены напрямую из маркета Добавить токены Функция NFC недоступна на вашем устройстве @@ -703,6 +712,7 @@ День Каждый день Каждую минуту + Каждые ~%1$s сек Эпоха Эра Час diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index c032f68459..416b7f49ec 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -39,7 +39,7 @@ Забагато спроб Ви вимкнули біометричну автентифікацію на своєму телефоні і не зможете зберігати гаманці в додатку. Щоб зберегти гаманці, будь ласка, увімкніть функцію біометричної автентифікації в налаштуваннях телефону. Почніть процес резервного копіювання - За допомогою банківської картки або банківського рахунку + Використовуйте банківську картку або інші способи оплати %d картка %d картки @@ -145,7 +145,7 @@ Перейменувати Зберегти Зберегти зміни - Шукати + Пошук Пошук токенів Seed-фраза Оберіть дію @@ -279,7 +279,7 @@ Токенів не знайдено. Будь ласка, спробуйте інший запит ID: %s ID транзакції скопійовано - З іншої валюти у вашому гаманці + Конвертуйте одну з ваших валют в іншу Інформація нижче не є обов\'язковою. Ви можете стерти її, якщо бажаєте. Розкажіть, яких функцій вам не вистачає, і ми спробуємо вам допомогти. Розкажіть, будь ласка, яку картку ви маєте? @@ -362,6 +362,7 @@ Не вдалося завантажити дані... Немає даних Швидкі дії + Шукати на маркеті Результат Переглянути токени до 100к USD ринкової капіталізації Показати токени @@ -394,24 +395,29 @@ Давлення покупця Циркуляційний запас Загальна кількість монет, які доступні для торгівлі та перебувають в обігу на ринку + Циркуляційний запас Досвідчені покупці Мережеві покупці з додатковою вимогою мати не менше 100 вихідних транзакцій + Досвідчені покупці Повн. розведена кап. Загальна теоретична вартість криптовалюти, якщо всі монети, які могли б існувати, перебувають в обігу, включаючи ті, що не перебувають в обігу в даний час Дата створення Високий Тримачі Зміна кількості власників токенів протягом певного періоду часу + Тримачі Інсайти Посилання Ліквідність Зміна того, скільки ліквідності доступно для токена протягом зазначеного періоду часу + Ліквідність Індекс ліквідності Низький Рин. кап. Загальна ринкова вартість криптовалюти, що розраховується шляхом множення поточної ціни монети на загальну кількість монет в обігу Рейтинг Позиція в крипторейтингу між усіма монетами на основі ринкової капіталізації + Рейтинг Максимальна пропозиція Метрики Офіційні посилання @@ -419,10 +425,12 @@ Репозиторій Оцінка безпеки Соцмережі - Загальна пропозиція + Загальна проп. Максимальна кількість монет або токенів, яка може коли-небудь існувати для певної криптовалюти + Загальна пропозиція Обсяг торгів (24г) Загальна сума криптовалюти, якою торгували протягом останніх 24 годин, що вказує на рівень активності та ліквідності на ринку + Обсяг торгів (24г) Потягніть вгору або торкніться панелі пошуку, щоб додати токени безпосередньо з маркету Додати токени Функція NFC недоступна на вашому пристрої @@ -530,7 +538,7 @@ Доступ до камери заборонено %1$s (%2$s) у мережі %3$s Надсилайте лише %s на цю адресу. Надсилання будь-якої іншої валюти призведе до її незворотної втрати. - Покажіть QR-код або поділіться своєю адресою + Перекажіть кошти з іншого гаманця або біржі Взяти участь Не вдалося завантажити інформацію по реферальній програмі. Будь ласка, спробуйте пізніше. Не вдалося завантажити інформацію по реферальній програмі. Код помилки: %s. Будь ласка, спробуйте пізніше. @@ -665,6 +673,8 @@ Сума для стейкінгу має бути не менше %s Сума стейкінгу буде округлена до %1$s TRX відповідно до правил мережі. Зняти кошти + Комісія за стейкінг-акаунт + Стейкінг-акаунт - це спеціальний рахунок, на якому зберігаються застейкані SOL токени. Він створюється, коли ви делегуєте свої токени валідатору для участі у перевірці транзакцій та отримання винагород. За створення стейкінг-акаунту стягується невелика комісія, яка повертається після завершення стейкінгу. Процентна ставка Річний відсоток, який ви можете отримати, беручи участь у стейкінгу. APR @@ -692,6 +702,7 @@ Перемістити Нативний стейкінг Зароблені винагороди будуть надіслані на ваш гаманець і доступні для використання відразу + Стейкайте безпечно та почніть отримувати ваші винагороди Стейкайте безпечно та почніть отримувати винагороди щоденно Стейкайте безпечно та почніть отримувати винагороди щогодини Стейкайте безпечно та почніть отримувати винагороди щомісяця @@ -720,6 +731,8 @@ Блок День Щодня + Щохвилини + Кожні ~%1$sсек Епоха Ера Година diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index e95c0bc2b7..c4e1616a0d 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -103,7 +103,7 @@ Enable Enabled Error - Exchange + Swap Explore Explore transaction history Explorer @@ -275,7 +275,7 @@ No tokens found. Please try another request ID: %s Transaction ID copied - Convert one of your currencies to another + Swap any asset in your portfolio for this token The following information is optional. You can erase it if you don\'t want to share it. Tell us what functions you are missing, and we will try to help you. Please tell us what card do you have @@ -387,24 +387,31 @@ Buy pressure Circulating supply The total number of coins that are available for trading and are circulating in the market + Circulating supply Experienced buyers Net buyers with the additional requirement of having at least 100 outgoing transactions + Experienced buyers Fully diluted valuation The total theoretical value of a cryptocurrency if all coins that could exist are in circulation, including those not currently circulating + Fully diluted valuation Genesis date High Holders The change in the number of token holders within a specific timeframe + Holders Insights Links Liquidity The change in how much liquidity is available for the token during the specified timeframe + Liquidity Liquidity index Low Market cap The total market value of a cryptocurrency, calculated by multiplying the current price of the coin by the total number of coins in circulation + Market cap Market rating Position in crypto rating between all coins based on market capitalization + Market rating Max supply Metrics Official links @@ -414,8 +421,10 @@ Social Total supply The maximum number of coins or tokens that can ever exist for a particular cryptocurrency + Total supply Trading volume (24h) The total amount of a cryptocurrency that has been traded within the last 24 hours, indicating the level of activity and liquidity in the market + Trading volume (24h) Pull this up or tap the search bar to add tokens directly from the market Add tokens NFC is not available on your device @@ -689,6 +698,7 @@ Staking allows you to earn %1$s. Your staking rewards arrive every week. Stake securely and start earning weekly rewards Earn staking rewards + Staking is currently unavailable due to network conditions. Please try again later. Staking in the %1$s network with a new validator will automatically transfer all previously staked funds to this validator Reinvests your earned rewards in your staked amount, increasing potential earnings. Unlock your money to withdraw it from staking process. Unlocking takes %s. @@ -707,8 +717,9 @@ Manual Block Day - Each day + Daily Each minute + Each ~%1$ssec Epoch Era Hour From 7eb16ddaff01a150cd4d81b859f6ea58ccf39584 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 14:37:30 +0300 Subject: [PATCH 05/18] Updated on 2026-08-14 --- .../impl/model/converters/InsightsConverter.kt | 8 ++++---- .../impl/model/converters/MetricsConverter.kt | 16 ++++++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt index adb9f89f72..fb93bd86a2 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt @@ -77,7 +77,7 @@ internal class InsightsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_experienced_buyers), + title = resourceReference(R.string.markets_token_details_experienced_buyers_full), body = resourceReference(R.string.markets_token_details_experienced_buyers_description), ), ) @@ -92,7 +92,7 @@ internal class InsightsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_buy_pressure), + title = resourceReference(R.string.markets_token_details_buy_pressure_full), body = resourceReference(R.string.markets_token_details_buy_pressure_description), ), ) @@ -107,7 +107,7 @@ internal class InsightsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_holders), + title = resourceReference(R.string.markets_token_details_holders_full), body = resourceReference(R.string.markets_token_details_holders_description), ), ) @@ -122,7 +122,7 @@ internal class InsightsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_liquidity), + title = resourceReference(R.string.markets_token_details_liquidity_full), body = resourceReference(R.string.markets_token_details_liquidity_description), ), ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt index 5cd11e6cd3..3501ff40f5 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt @@ -32,7 +32,9 @@ internal class MetricsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_market_capitalization), + title = resourceReference( + R.string.markets_token_details_market_capitalization_full, + ), body = resourceReference( R.string.markets_token_details_market_capitalization_description, ), @@ -46,7 +48,7 @@ internal class MetricsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_market_rating), + title = resourceReference(R.string.markets_token_details_market_rating_full), body = resourceReference(R.string.markets_token_details_market_rating_description), ), ) @@ -58,7 +60,7 @@ internal class MetricsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_trading_volume), + title = resourceReference(R.string.markets_token_details_trading_volume_full), body = resourceReference( R.string.markets_token_details_trading_volume_24h_description, ), @@ -72,7 +74,9 @@ internal class MetricsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_fully_diluted_valuation), + title = resourceReference( + R.string.markets_token_details_fully_diluted_valuation_full, + ), body = resourceReference( R.string.markets_token_details_fully_diluted_valuation_description, ), @@ -86,7 +90,7 @@ internal class MetricsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_circulating_supply), + title = resourceReference(R.string.markets_token_details_circulating_supply_full), body = resourceReference( R.string.markets_token_details_circulating_supply_description, ), @@ -100,7 +104,7 @@ internal class MetricsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_total_supply), + title = resourceReference(R.string.markets_token_details_total_supply_full), body = resourceReference(R.string.markets_token_details_total_supply_description), ), ) From 025c2f3857c90b8a0545c4071d6f7369a2a5c6f6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 14:37:41 +0300 Subject: [PATCH 06/18] Updated on 2026-08-14 --- .../tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt index 58247416e1..3637c9ed61 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt @@ -127,7 +127,7 @@ private fun AddFirstTokenContent(state: MyPortfolioUM.AddFirstToken, modifier: M verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { Text( - text = "To start buying, exchanging or receiving this asset, add this token to at least 1 network", // FIXME + text = stringResource(R.string.markets_add_to_my_portfolio_description), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) From 60cc956d086c74d10dc02b341c176153f0d74bc8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 14:37:50 +0300 Subject: [PATCH 07/18] Updated on 2026-08-14 --- .../markets/details/impl/ui/components/InfoBottomSheet.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt index f0d31afa7b..594d0f81df 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt @@ -25,7 +25,6 @@ internal fun InfoBottomSheet(config: TangemBottomSheetConfig) { TangemBottomSheet( config = config, - skipPartiallyExpanded = false, addBottomInsets = false, title = { TangemBottomSheetTitle(title = it.title) }, content = { From b76b5b8a9d5f57780131455b0b59179082405ad4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 16:52:41 +0500 Subject: [PATCH 08/18] Updated on 2026-08-14 --- .../state/transformers/SetInitialDataStateTransformer.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index 517cbba094..e20ee87384 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -19,7 +19,6 @@ import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType import com.tangem.features.staking.impl.presentation.state.converters.RewardsValidatorStateConverter import com.tangem.features.staking.impl.presentation.state.converters.YieldBalancesConverter import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents -import com.tangem.lib.crypto.BlockchainUtils.isCosmos import com.tangem.lib.crypto.BlockchainUtils.isPolkadot import com.tangem.utils.Provider import com.tangem.utils.isNullOrZero @@ -255,13 +254,7 @@ internal class SetInitialDataStateTransformer( private fun getRewardScheduleText(rewardSchedule: Yield.Metadata.RewardSchedule): TextReference? { return when (rewardSchedule) { - Yield.Metadata.RewardSchedule.BLOCK -> { - val networkId = cryptoCurrencyStatusProvider().currency.network.id.value - when { - isCosmos(networkId) -> resourceReference(R.string.staking_reward_schedule_each_minute) - else -> resourceReference(R.string.staking_reward_schedule_each_day) - } - } + Yield.Metadata.RewardSchedule.BLOCK -> resourceReference(R.string.staking_reward_schedule_block) Yield.Metadata.RewardSchedule.WEEK -> resourceReference(R.string.staking_reward_schedule_week) Yield.Metadata.RewardSchedule.HOUR -> resourceReference(R.string.staking_reward_schedule_hour) Yield.Metadata.RewardSchedule.DAY -> resourceReference(R.string.staking_reward_schedule_each_day) From bd451cf615bb1b89bbd1fc0cd4b10c2f0d8ffec8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 15:01:28 +0300 Subject: [PATCH 09/18] Updated on 2026-08-14 --- .../impl/model/AddToPortfolioBSContentUMFactory.kt | 11 ++++++++--- .../portfolio/impl/model/MarketsPortfolioModel.kt | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt index 3ce401a818..80f3e39d33 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt @@ -20,7 +20,7 @@ import kotlinx.collections.immutable.toImmutableList * @property onAddToPortfolioVisibilityChange callback is invoked when add to portfolio visibility is changed * @property onWalletSelectorVisibilityChange callback is invoked when wallet selector visibility is changed * @property onNetworkSwitchClick callback is invoked when network switch is clicked - * @property onWalletSelect callback is invoked when wallet is selected + * @property onAnotherWalletSelect callback is invoked when wallet is selected * @property onContinueClick callback is invoked when continue button is clicked * [REDACTED_AUTHOR] @@ -30,7 +30,7 @@ internal class AddToPortfolioBSContentUMFactory( private val onAddToPortfolioVisibilityChange: (Boolean) -> Unit, private val onWalletSelectorVisibilityChange: (Boolean) -> Unit, private val onNetworkSwitchClick: (BlockchainRowUM, Boolean) -> Unit, - private val onWalletSelect: (UserWalletId) -> Unit, + private val onAnotherWalletSelect: (UserWalletId) -> Unit, private val onContinueClick: (selectedWalletId: UserWalletId, addedNetworks: Set) -> Unit, ) { @@ -112,7 +112,12 @@ internal class AddToPortfolioBSContentUMFactory( val balance = portfolioData.walletsWithBalance[userWallet.walletId] UserWalletItemUMConverter( - onClick = onWalletSelect, + onClick = { + if (it != selectedWalletId) { + onAnotherWalletSelect(it) + onWalletSelectorVisibilityChange(false) + } + }, appCurrency = portfolioData.appCurrency, balance = balance?.getOrNull(), isLoading = balance?.isLoading() == true, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt index 9c6c4bcba1..aff1bf94a8 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt @@ -92,7 +92,7 @@ internal class MarketsPortfolioModel @Inject constructor( onAddToPortfolioVisibilityChange = ::onAddToPortfolioBSVisibilityChange, onWalletSelectorVisibilityChange = ::onWalletSelectorVisibilityChange, onNetworkSwitchClick = ::onNetworkSwitchClick, - onWalletSelect = { + onAnotherWalletSelect = { onWalletSelect(it) // === Analytics === analyticsEventHandler.send( From 8e5aef680b529f4abc1cb68407f779bcf82efa80 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 15:08:32 +0300 Subject: [PATCH 10/18] Updated on 2026-08-14 --- .../details/impl/ui/components/Description.kt | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt index aca7dd32ae..987689d400 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt @@ -1,8 +1,8 @@ package com.tangem.features.markets.details.impl.ui.components +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -38,17 +38,16 @@ internal fun Description( } } - ClickableText( - modifier = modifier, + Text( + modifier = modifier + .clickable( + interactionSource = null, + indication = null, + onClick = onReadMoreClick, + ), text = text, style = TangemTheme.typography.body2, - ) { - text.spanStyles.getOrNull(1)?.let { spanStyle -> - if (it in spanStyle.start..spanStyle.end) { - onReadMoreClick() - } - } - } + ) } else { Text( modifier = modifier, From e1a4d99a8e937fc0387651e49d9fa22b33c921a3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 17:46:49 +0500 Subject: [PATCH 11/18] Updated on 2026-08-14 --- .../impl/presentation/viewmodel/StakingViewModel.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index cebace0c6e..07b5e0414f 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -1,10 +1,7 @@ package com.tangem.features.staking.impl.presentation.viewmodel import android.os.Bundle -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.* import arrow.core.getOrElse import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.routing.AppRoute @@ -161,7 +158,7 @@ internal class StakingViewModel @Inject constructor( private val stakingEventFactory: StakingEventFactory get() = StakingEventFactory( stateController = stateController, - popBackStack = stakingStateRouter::onBackClick, + popBackStack = ::onBackClick, onFailedTxEmailClick = ::onFailedTxEmailClick, ) From 4a726e436c4d38a69802b32e4b5cfff21033204a Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 16:40:25 +0300 Subject: [PATCH 12/18] Updated on 2026-08-14 --- .../tap/di/domain/StakingDomainModule.kt | 37 +++++++++++++ .../components/inputrow/InputRowImageBase.kt | 42 ++++++++++++--- .../components/inputrow/InputRowImageInfo.kt | 21 ++++++++ .../ic_staking_pending_transaction.xml | 19 +++++++ ...aultStakingPendingTransactionRepository.kt | 27 ++++++++++ .../PendingTransactionItemConverter.kt | 22 ++++++++ .../converters/YieldBalanceConverter.kt | 5 +- .../data/staking/di/StakingDataModule.kt | 10 +++- .../staking/model/PendingTransaction.kt | 13 +++++ .../domain/staking/model/SubmitHashData.kt | 14 +++++ .../staking/model/stakekit/YieldBalance.kt | 5 +- .../staking/GetPendingTransactionsUseCase.kt | 24 +++++++++ .../InvalidatePendingTransactionsUseCase.kt | 54 +++++++++++++++++++ .../staking/SavePendingTransactionUseCase.kt | 24 +++++++++ .../domain/staking/SubmitHashUseCase.kt | 7 +-- .../StakingPendingTransactionRepository.kt | 13 +++++ .../state/InnerYieldBalanceState.kt | 3 +- .../impl/presentation/state/StakingUiState.kt | 1 + .../state/converters/BalanceItemConverter.kt | 6 ++- .../RewardsValidatorStateConverter.kt | 3 +- .../converters/YieldBalancesConverter.kt | 5 +- .../state/helpers/StakingTransactionSender.kt | 47 +++++++++++++--- .../ConfirmationStatePreviewData.kt | 1 + .../previewdata/InitialStakingStatePreview.kt | 3 +- .../state/stub/StakingClickIntentsStub.kt | 4 +- .../SetBalanceStateTransformer.kt | 18 +++++++ .../SetConfirmationStateLoadingTransformer.kt | 1 + .../SetInitialDataStateTransformer.kt | 4 ++ .../ui/StakingInitialInfoContent.kt | 8 +-- .../viewmodel/StakingClickIntents.kt | 1 + .../viewmodel/StakingViewModel.kt | 16 ++++++ 31 files changed, 421 insertions(+), 37 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_staking_pending_transaction.xml create mode 100644 data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt create mode 100644 data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt create mode 100644 domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/PendingTransaction.kt create mode 100644 domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/SubmitHashData.kt create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/GetPendingTransactionsUseCase.kt create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetBalanceStateTransformer.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt index c6d4c83b91..0807a628f1 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt @@ -2,6 +2,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.staking.* import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.staking.repositories.StakingTransactionHashRepository import dagger.Module @@ -122,6 +123,42 @@ internal object StakingDomainModule { ) } + @Provides + @Singleton + fun provideSavePendingTransactionUseCase( + stakingPendingTransactionRepository: StakingPendingTransactionRepository, + stakingErrorResolver: StakingErrorResolver, + ): SavePendingTransactionUseCase { + return SavePendingTransactionUseCase( + stakingPendingTransactionRepository = stakingPendingTransactionRepository, + stakingErrorResolver = stakingErrorResolver, + ) + } + + @Provides + @Singleton + fun provideInvalidatePendingTransactionsUseCase( + stakingPendingTransactionRepository: StakingPendingTransactionRepository, + stakingErrorResolver: StakingErrorResolver, + ): InvalidatePendingTransactionsUseCase { + return InvalidatePendingTransactionsUseCase( + stakingPendingTransactionRepository = stakingPendingTransactionRepository, + stakingErrorResolver = stakingErrorResolver, + ) + } + + @Provides + @Singleton + fun provideGetPendingTransactionsUseCase( + stakingPendingTransactionRepository: StakingPendingTransactionRepository, + stakingErrorResolver: StakingErrorResolver, + ): GetPendingTransactionsUseCase { + return GetPendingTransactionsUseCase( + stakingPendingTransactionRepository = stakingPendingTransactionRepository, + stakingErrorResolver = stakingErrorResolver, + ) + } + @Provides @Singleton fun provideSendUnsubmittedHashesUseCase( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt index 6db1972160..bd65f92ab5 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt @@ -24,6 +24,7 @@ import com.tangem.core.ui.components.inputrow.inner.InputRowAsyncImage import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveAnnotatedReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -38,10 +39,12 @@ import com.tangem.core.ui.res.TangemThemePreview * @param subtitleColor subtitle text color * @param captionColor caption text color * @param isGrayscaleImage whether to display grayscale image + * @param subtitleEndIconRes icon to show after subtitle * @param iconEndRes icon to end of row * @param onImageError composable to show if image loading failed * @param extraContent extra content */ +@Suppress("LongMethod") @Composable internal fun InputRowImageBase( subtitle: TextReference, @@ -53,7 +56,9 @@ internal fun InputRowImageBase( captionColor: Color = TangemTheme.colors.text.tertiary, iconTint: Color = TangemTheme.colors.icon.informative, isGrayscaleImage: Boolean = false, - iconEndRes: Int? = null, + @DrawableRes subtitleEndIconRes: Int? = null, + subtitleEndIconTint: Color = TangemColorPalette.Azure, + @DrawableRes iconEndRes: Int? = null, onImageError: (@Composable () -> Unit)? = null, extraContent: (@Composable RowScope.() -> Unit)? = null, ) { @@ -89,11 +94,17 @@ internal fun InputRowImageBase( SpacerW12() } Column { - Text( - text = subtitle.resolveReference(), - style = TangemTheme.typography.subtitle2, - color = subtitleColor, - ) + Row { + Text( + text = subtitle.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = subtitleColor, + ) + SubtitleEndIconRes( + subtitleEndIconRes = subtitleEndIconRes, + subtitleEndIconTint = subtitleEndIconTint, + ) + } if (caption != null) { Text( text = caption.resolveAnnotatedReference(), @@ -113,11 +124,28 @@ internal fun InputRowImageBase( } } +@Composable +private fun RowScope.SubtitleEndIconRes(subtitleEndIconRes: Int?, subtitleEndIconTint: Color) { + AnimatedVisibility( + visible = subtitleEndIconRes != null, + label = "Subtitle end icon visibility animation", + modifier = Modifier.align(Alignment.CenterVertically), + ) { + val icon = remember(this) { requireNotNull(subtitleEndIconRes) } + Icon( + painter = rememberVectorPainter(image = ImageVector.vectorResource(id = icon)), + tint = subtitleEndIconTint, + contentDescription = null, + modifier = Modifier.padding(start = TangemTheme.dimens.spacing4), + ) + } +} + @Composable private fun RowScope.InputRowEndIcon(iconRes: Int?) { AnimatedVisibility( visible = iconRes != null, - label = "Icon visibility animation", + label = "End icon visibility animation", ) { val icon = remember(this) { requireNotNull(iconRes) } Icon( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt index 2905e007b7..366bd4e48d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt @@ -34,6 +34,7 @@ import com.tangem.core.ui.res.TangemThemePreview * @param subtitleColor subtitle text color * @param captionColor caption text color * @param isGrayscaleImage whether to display grayscale image + * @param showPendingIcon whether to show pending icon * @param iconEndRes icon to end of row * @param onImageError composable to show if image loading failed */ @@ -52,6 +53,7 @@ fun InputRowImageInfo( captionColor: Color = TangemTheme.colors.text.tertiary, iconTint: Color = TangemTheme.colors.icon.informative, isGrayscaleImage: Boolean = false, + @DrawableRes subtitleEndIconRes: Int? = null, @DrawableRes iconEndRes: Int? = null, onImageError: (@Composable () -> Unit)? = null, ) { @@ -76,6 +78,7 @@ fun InputRowImageInfo( subtitleColor = subtitleColor, captionColor = captionColor, isGrayscaleImage = isGrayscaleImage, + subtitleEndIconRes = subtitleEndIconRes, iconEndRes = iconEndRes, onImageError = onImageError, ) { @@ -132,6 +135,7 @@ private fun InputRowImageInfo_Preview( infoTitle = data.infoTitle, infoSubtitle = data.infoSubtitle, imageUrl = "", + subtitleEndIconRes = data.subtitleEndIconRes, iconEndRes = R.drawable.ic_chevron_right_24, ) } @@ -162,6 +166,22 @@ private class InputRowImageInfoPreviewDataProvider : PreviewParameterProvider + + + + diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt new file mode 100644 index 0000000000..0b637744ab --- /dev/null +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt @@ -0,0 +1,27 @@ +package com.tangem.data.staking + +import com.tangem.domain.staking.model.PendingTransaction +import com.tangem.domain.staking.model.stakekit.BalanceItem +import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository +import java.util.concurrent.CopyOnWriteArrayList + +internal class DefaultStakingPendingTransactionRepository : StakingPendingTransactionRepository { + + private val pendingTransactions = CopyOnWriteArrayList() + + override fun saveTransaction(transaction: PendingTransaction) { + pendingTransactions.add(transaction) + } + + override fun removeTransactions(transactions: Set) { + pendingTransactions.removeAll(transactions) + } + + override fun getTransactionsWithBalanceItems(): List> { + return pendingTransactions.mapNotNull { pendingTransaction -> + PendingTransactionItemConverter.convert(pendingTransaction)?.let { balanceItem -> + pendingTransaction to balanceItem + } + } + } +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt new file mode 100644 index 0000000000..a18b61f591 --- /dev/null +++ b/data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt @@ -0,0 +1,22 @@ +package com.tangem.data.staking + +import com.tangem.domain.staking.model.PendingTransaction +import com.tangem.domain.staking.model.stakekit.BalanceItem +import com.tangem.utils.converter.Converter +import org.joda.time.DateTime + +internal object PendingTransactionItemConverter : Converter { + + override fun convert(value: PendingTransaction): BalanceItem? { + return BalanceItem( + groupId = value.groupId ?: return null, + type = value.type ?: return null, + amount = value.amount ?: return null, + rawCurrencyId = value.rawCurrencyId, + validatorAddress = value.validator?.address, + date = DateTime.now(), + pendingActions = emptyList(), + isPending = true, + ) + } +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt index 90b35354b7..5a7f56b25e 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt @@ -19,12 +19,10 @@ internal class YieldBalanceConverter : Converter BalanceItem( - id = item.groupId, + groupId = item.groupId, type = BalanceType.valueOf(item.type.name), amount = item.amount, - pricePerShare = item.pricePerShare, rawCurrencyId = item.tokenDTO.coinGeckoId, - rawNetworkId = item.tokenDTO.network.name, // tron-specific. operates validatorAddresses instead of validatorAddress validatorAddress = item.validatorAddress ?: item.validatorAddresses?.get(0), date = item.date?.toDateTime(), @@ -32,6 +30,7 @@ internal class YieldBalanceConverter : Converter, + val isPending: Boolean, ) data class PendingAction( diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetPendingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetPendingTransactionsUseCase.kt new file mode 100644 index 0000000000..1f4be9dea2 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetPendingTransactionsUseCase.kt @@ -0,0 +1,24 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.model.stakekit.BalanceItem +import com.tangem.domain.staking.model.stakekit.StakingError +import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository + +/** + * Use case for getting saved staking pending transactions. + */ +class GetPendingTransactionsUseCase( + private val stakingPendingTransactionRepository: StakingPendingTransactionRepository, + private val stakingErrorResolver: StakingErrorResolver, +) { + + operator fun invoke(): Either> { + return Either.catch { + stakingPendingTransactionRepository.getTransactionsWithBalanceItems().map { it.second } + }.mapLeft { + stakingErrorResolver.resolve(it) + } + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt new file mode 100644 index 0000000000..daddce0ad9 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt @@ -0,0 +1,54 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.model.PendingTransaction +import com.tangem.domain.staking.model.stakekit.BalanceItem +import com.tangem.domain.staking.model.stakekit.StakingError +import com.tangem.domain.staking.model.stakekit.YieldBalance +import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository + +class InvalidatePendingTransactionsUseCase( + private val stakingPendingTransactionRepository: StakingPendingTransactionRepository, + private val stakingErrorResolver: StakingErrorResolver, +) { + + operator fun invoke(yieldBalance: YieldBalance): Either> { + return Either.catch { + if (yieldBalance is YieldBalance.Data) { + val (balancesToDisplay, transactionsToRemove) = mergeRealAndPendingTransactions( + real = yieldBalance.balance.items, + pending = stakingPendingTransactionRepository.getTransactionsWithBalanceItems(), + ) + + stakingPendingTransactionRepository.removeTransactions(transactionsToRemove.toSet()) + + balancesToDisplay + } else { + emptyList() + } + }.mapLeft { + stakingErrorResolver.resolve(it) + } + } + + private fun mergeRealAndPendingTransactions( + real: List, + pending: List>, + ): Pair, List> { + val map = real.associateBy { Triple(it.groupId, it.type, it.amount) }.toMutableMap() + + val toRemove = mutableListOf() + + pending.forEach { (pendingTransaction, balanceItem) -> + val key = Triple(balanceItem.groupId, balanceItem.type, balanceItem.amount) + if (map.containsKey(key)) { + map[key] = balanceItem + } else { + toRemove.add(pendingTransaction) + } + } + + return map.values.toList() to toRemove + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt new file mode 100644 index 0000000000..64ce855ae5 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt @@ -0,0 +1,24 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.model.PendingTransaction +import com.tangem.domain.staking.model.stakekit.StakingError +import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository + +/** + * Use case for saving hash that failed to submit during staking confirmation + */ +class SavePendingTransactionUseCase( + private val stakingPendingTransactionRepository: StakingPendingTransactionRepository, + private val stakingErrorResolver: StakingErrorResolver, +) { + + operator fun invoke(pendingTransaction: PendingTransaction): Either { + return Either.catch { + stakingPendingTransactionRepository.saveTransaction(pendingTransaction) + }.mapLeft { + stakingErrorResolver.resolve(it) + } + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt index 83d1b151b7..fba8daa29b 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.domain.staking import arrow.core.Either +import com.tangem.domain.staking.model.SubmitHashData import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.repositories.StakingErrorResolver import com.tangem.domain.staking.repositories.StakingTransactionHashRepository @@ -13,12 +14,12 @@ class SubmitHashUseCase( private val stakingErrorResolver: StakingErrorResolver, ) { - suspend fun submitHash(transactionId: String, transactionHash: String): Either { + suspend operator fun invoke(submitHashData: SubmitHashData): Either { return Either .catch { stakingTransactionHashRepository.submitHash( - transactionId = transactionId, - transactionHash = transactionHash, + transactionId = submitHashData.transactionId, + transactionHash = submitHashData.transactionHash, ) }.mapLeft { stakingErrorResolver.resolve(it) diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt new file mode 100644 index 0000000000..02bb96806b --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.staking.repositories + +import com.tangem.domain.staking.model.PendingTransaction +import com.tangem.domain.staking.model.stakekit.BalanceItem + +interface StakingPendingTransactionRepository { + + fun getTransactionsWithBalanceItems(): List> + + fun saveTransaction(transaction: PendingTransaction) + + fun removeTransactions(transactions: Set) +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt index c989de7fb1..92c74bd946 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt @@ -22,7 +22,7 @@ internal sealed class InnerYieldBalanceState { @Immutable internal data class BalanceState( - val id: String, + val groupId: String, val title: TextReference, val type: BalanceType, val subtitle: TextReference?, @@ -34,4 +34,5 @@ internal data class BalanceState( val rawCurrencyId: String?, val validator: Yield.Validator?, val pendingActions: ImmutableList, + val isPending: Boolean, ) \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt index 9e4db06b4e..c490284116 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt @@ -98,6 +98,7 @@ internal sealed class StakingStates { val transactionDoneState: TransactionDoneState, val isApprovalNeeded: Boolean, val reduceAmountBy: BigDecimal?, + val balanceState: BalanceState?, ) : ConfirmationState() data class Empty( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt index 9fd91d3cb8..141759adfb 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt @@ -22,6 +22,7 @@ internal class BalanceItemConverter( private val appCurrencyProvider: Provider, private val yield: Yield, ) : Converter { + override fun convert(value: BalanceItem): BalanceState? { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val appCurrency = appCurrencyProvider() @@ -37,7 +38,7 @@ internal class BalanceItemConverter( val title = value.type.getTitle(validator?.name) return title?.let { BalanceState( - id = value.id, + groupId = value.groupId, validator = validator, title = title, subtitle = getSubtitle(value), @@ -59,7 +60,8 @@ internal class BalanceItemConverter( ), rawCurrencyId = value.rawCurrencyId, pendingActions = value.pendingActions.toPersistentList(), - isClickable = value.type.isClickable(), + isClickable = value.type.isClickable() && !value.isPending, + isPending = value.isPending, ) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt index e3cc2f8e07..295f07c032 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt @@ -78,7 +78,7 @@ internal class RewardsValidatorStateConverter( ) return BalanceState( - id = balance.id, + groupId = balance.groupId, validator = this, title = stringReference(this.name), subtitle = null, @@ -90,6 +90,7 @@ internal class RewardsValidatorStateConverter( pendingActions = balance.pendingActions.toPersistentList(), isClickable = true, type = balance.type, + isPending = balance.isPending, ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index ae45ca8d1f..5c82de46c3 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -14,6 +14,7 @@ import kotlinx.collections.immutable.toPersistentList internal class YieldBalancesConverter( private val cryptoCurrencyStatusProvider: Provider, private val appCurrencyProvider: Provider, + private val balancesToShowProvider: Provider>, private val yield: Yield, ) : Converter { @@ -32,6 +33,8 @@ internal class YieldBalancesConverter( val cryptoRewardsValue = yieldBalance.getRewardStakingBalance() val fiatRewardsValue = cryptoCurrencyStatus.value.fiatRate?.times(cryptoRewardsValue) + val balanceToShowItems = balancesToShowProvider() + InnerYieldBalanceState.Data( rewardsCrypto = BigDecimalFormatter.formatCryptoAmount( cryptoAmount = cryptoRewardsValue, @@ -43,7 +46,7 @@ internal class YieldBalancesConverter( fiatCurrencySymbol = appCurrency.symbol, ), rewardBlockType = getRewardBlockType(), - balance = yieldBalance.balance.items.mapBalances(), + balance = balanceToShowItems.mapBalances(), ) } else { InnerYieldBalanceState.Empty diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt index e6e0b2b6e0..109dafb912 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt @@ -5,10 +5,10 @@ import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.Fee import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.domain.staking.GetConstructedStakingTransactionUseCase -import com.tangem.domain.staking.GetStakingTransactionUseCase -import com.tangem.domain.staking.SaveUnsubmittedHashUseCase -import com.tangem.domain.staking.SubmitHashUseCase +import com.tangem.domain.staking.* +import com.tangem.domain.staking.model.PendingTransaction +import com.tangem.domain.staking.model.SubmitHashData +import com.tangem.domain.staking.model.stakekit.BalanceType import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.Yield @@ -45,6 +45,7 @@ internal class StakingTransactionSender @AssistedInject constructor( private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val submitHashUseCase: SubmitHashUseCase, private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase, + private val savePendingTransactionUseCase: SavePendingTransactionUseCase, private val analyticsEventHandler: AnalyticsEventHandler, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, @Assisted private val userWallet: UserWallet, @@ -89,6 +90,7 @@ internal class StakingTransactionSender @AssistedInject constructor( sendStakingTransaction( fullTransactionsData = fullTransactionsData, + balanceState = confirmationState.balanceState, onSendSuccess = onSendSuccess, onSendError = onSendError, ) @@ -191,6 +193,7 @@ internal class StakingTransactionSender @AssistedInject constructor( private suspend fun sendStakingTransaction( fullTransactionsData: List, + balanceState: BalanceState?, onSendSuccess: (txUrl: String) -> Unit, onSendError: (SendTransactionError?) -> Unit, ) { @@ -206,6 +209,11 @@ internal class StakingTransactionSender @AssistedInject constructor( submitHash( transactionIds = fullTransactionsData.map { it.stakeKitTransaction.id }, transactionHashes = transactionHashes, + groupId = balanceState?.groupId, + validator = balanceState?.validator, + amount = balanceState?.cryptoDecimal, + balanceType = balanceState?.type, + rawCurrencyId = balanceState?.rawCurrencyId, ) val txUrl = getExplorerTransactionUrlUseCase( txHash = transactionHashes.last(), @@ -218,13 +226,27 @@ internal class StakingTransactionSender @AssistedInject constructor( ) } - private suspend fun submitHash(transactionIds: List, transactionHashes: List) { + private suspend fun submitHash( + transactionIds: List, + transactionHashes: List, + groupId: String?, + validator: Yield.Validator?, + amount: BigDecimal?, + balanceType: BalanceType?, + rawCurrencyId: String?, + ) { transactionIds .zip(transactionHashes) .forEach { (transactionId, transactionHash) -> - submitHashUseCase.submitHash( - transactionId = transactionId, - transactionHash = transactionHash, + submitHashUseCase( + SubmitHashData( + transactionId = transactionId, + transactionHash = transactionHash, + validator = validator, + amount = amount, + balanceType = balanceType, + rawCurrencyId = rawCurrencyId, + ), ) .onLeft { analyticsEventHandler.send( @@ -236,6 +258,15 @@ internal class StakingTransactionSender @AssistedInject constructor( ) }.onRight { Timber.d("Successful hash submission") + savePendingTransactionUseCase.invoke( + PendingTransaction( + groupId = groupId, + type = balanceType, + amount = amount, + rawCurrencyId = rawCurrencyId, + validator = validator, + ), + ) } } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt index b5b6e9061f..51b7ec69a2 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt @@ -93,5 +93,6 @@ internal object ConfirmationStatePreviewData { isApprovalNeeded = false, reduceAmountBy = null, pendingActions = null, + balanceState = null, ) } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt index 6ad1e50645..e42df87aba 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt @@ -68,7 +68,7 @@ internal object InitialStakingStatePreview { rewardBlockType = RewardBlockType.RewardUnavailable, balance = persistentListOf( BalanceState( - id = "id", + groupId = "groupId", title = stringReference("Binance"), cryptoValue = "100", cryptoAmount = stringReference("100 SOL"), @@ -91,6 +91,7 @@ internal object InitialStakingStatePreview { isClickable = true, type = BalanceType.STAKED, subtitle = null, + isPending = false, ), ), ), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt index b4c0e2a299..66ec803e8a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt @@ -20,8 +20,8 @@ internal object StakingClickIntentsStub : StakingClickIntents { actionTypeToOverwrite: StakingActionCommonType?, pendingAction: PendingAction?, pendingActions: ImmutableList?, - ) { - } + balanceState: BalanceState?, + ) { } override fun onActionClick() {} diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetBalanceStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetBalanceStateTransformer.kt new file mode 100644 index 0000000000..d6fae7cd7f --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetBalanceStateTransformer.kt @@ -0,0 +1,18 @@ +package com.tangem.features.staking.impl.presentation.state.transformers + +import com.tangem.features.staking.impl.presentation.state.* +import com.tangem.utils.transformer.Transformer + +internal class SetBalanceStateTransformer( + private val balanceState: BalanceState? = null, +) : Transformer { + + override fun transform(prevState: StakingUiState): StakingUiState { + val possibleConfirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data + ?: return prevState + + return prevState.copy( + confirmationState = possibleConfirmationState.copy(balanceState = balanceState), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt index 27d5d30086..c2df5db972 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt @@ -41,6 +41,7 @@ internal class SetConfirmationStateLoadingTransformer( pendingActions = possibleConfirmationState?.pendingActions, isApprovalNeeded = false, reduceAmountBy = null, + balanceState = null, ), ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index e20ee87384..66b56ea7aa 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -10,6 +10,7 @@ import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.core.serialization.SerializedBigDecimal +import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet @@ -37,6 +38,7 @@ internal class SetInitialDataStateTransformer( private val cryptoCurrencyStatusProvider: Provider, private val userWalletProvider: Provider, private val appCurrencyProvider: Provider, + private val balancesToShowProvider: Provider>, ) : Transformer { private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) @@ -59,6 +61,7 @@ internal class SetInitialDataStateTransformer( YieldBalancesConverter( cryptoCurrencyStatusProvider, appCurrencyProvider, + balancesToShowProvider, yield, ) } @@ -228,6 +231,7 @@ internal class SetInitialDataStateTransformer( pendingActions = null, isApprovalNeeded = isApprovalNeeded, reduceAmountBy = null, + balanceState = null, ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index 679dd0675d..589c784974 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -89,7 +89,7 @@ internal fun StakingInitialInfoContent( if (state.showBanner) { item(key = BANNER_BLOCK_KEY) { Column( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), ) { BannerBlock(onClick = clickIntents::onInitialInfoBannerClick) SpacerH12() @@ -105,7 +105,7 @@ internal fun StakingInitialInfoContent( if (state.yieldBalance is InnerYieldBalanceState.Data) { item(key = STAKING_REWARD_BLOCK_KEY) { - Column(modifier = Modifier.animateItemPlacement()) { + Column(modifier = Modifier.animateItem()) { StakingRewardBlock( rewardCrypto = state.yieldBalance.rewardsCrypto, rewardFiat = state.yieldBalance.rewardsFiat, @@ -138,7 +138,6 @@ internal fun StakingInitialInfoContent( } } -@OptIn(ExperimentalFoundationApi::class) private fun LazyListScope.activeStakingBlock( state: StakingStates.InitialInfoState.Data, clickIntents: StakingClickIntents, @@ -175,7 +174,7 @@ private fun LazyListScope.activeStakingBlock( onClick = clickIntents::onActiveStake, onAnalytic = clickIntents::onActiveStakeAnalytic, modifier = Modifier - .animateItemPlacement() + .animateItem() .then( if (state.yieldBalance.balance.last() == balance) { Modifier.clip(CornersToRound.BOTTOM_2.getShape()) @@ -279,6 +278,7 @@ private fun ActiveStakingBlock( imageUrl = balance.getImage(), iconRes = icon, iconTint = iconTint, + subtitleEndIconRes = R.drawable.ic_staking_pending_transaction.takeIf { balance.isPending }, onImageError = { ValidatorImagePlaceholder() }, modifier = modifier .background(TangemTheme.colors.background.action) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt index c6fe6235d4..4cad63c4c7 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt @@ -20,6 +20,7 @@ internal interface StakingClickIntents : AmountScreenClickIntents { actionTypeToOverwrite: StakingActionCommonType? = null, pendingAction: PendingAction? = null, pendingActions: ImmutableList? = null, + balanceState: BalanceState? = null, ) fun onActionClick() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index cebace0c6e..a689d68b40 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -23,9 +23,11 @@ import com.tangem.domain.feedback.GetCardInfoUseCase import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.domain.staking.InvalidatePendingTransactionsUseCase import com.tangem.domain.staking.IsAnyTokenStakedUseCase import com.tangem.domain.staking.IsApproveNeededUseCase import com.tangem.domain.staking.model.StakingApproval +import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.YieldBalance @@ -98,6 +100,7 @@ internal class StakingViewModel @Inject constructor( private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase, private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase, private val isAnyTokenStakedUseCase: IsAnyTokenStakedUseCase, + private val invalidatePendingTransactionsUseCase: InvalidatePendingTransactionsUseCase, private val stakingTransactionLoader: StakingTransactionSender.Factory, private val stakingFeeTransactionLoader: StakingFeeTransactionLoader.Factory, private val stakingBalanceUpdater: StakingBalanceUpdater.Factory, @@ -131,6 +134,13 @@ internal class StakingViewModel @Inject constructor( private var userWallet: UserWallet by Delegates.notNull() private var appCurrency: AppCurrency by Delegates.notNull() + private val balancesToShow: List + get() { + return cryptoCurrencyStatus.value.yieldBalance?.let { + invalidatePendingTransactionsUseCase(it).getOrElse { emptyList() } + } ?: emptyList() + } + private var isInitialInfoAnalyticSent: Boolean = false private val balanceUpdater by lazy(LazyThreadSafetyMode.NONE) { @@ -200,6 +210,7 @@ internal class StakingViewModel @Inject constructor( actionTypeToOverwrite: StakingActionCommonType?, pendingAction: PendingAction?, pendingActions: ImmutableList?, + balanceState: BalanceState?, ) { if (actionTypeToOverwrite != null) { stateController.update(SetActionToExecuteTransformer(actionTypeToOverwrite, pendingAction, pendingActions)) @@ -208,10 +219,12 @@ internal class StakingViewModel @Inject constructor( when { isInitState() -> { stateController.update(SetConfirmationStateLoadingTransformer(yield, appCurrency)) + stateController.update(SetBalanceStateTransformer(balanceState)) onRefreshSwipe(isRefreshing = false) } isAssentState() -> { getFee(pendingAction, pendingActions) + stateController.update(SetBalanceStateTransformer(balanceState)) val amountState = value.amountState as? AmountState.Data if (amountState?.amountTextField?.isWarning == true) { stateController.update( @@ -413,6 +426,7 @@ internal class StakingViewModel @Inject constructor( onNextClick( actionTypeToOverwrite = StakingActionCommonType.PENDING_OTHER, pendingAction = action, + balanceState = activeStake, ) stateController.update(DismissBottomSheetStateTransformer) }, @@ -428,6 +442,7 @@ internal class StakingViewModel @Inject constructor( actionTypeToOverwrite = null, pendingAction = activeStake.pendingActions.firstOrNull(), pendingActions = activeStake.pendingActions.takeIf { isAllWithdrawActions }, + balanceState = activeStake, ) } } @@ -749,6 +764,7 @@ internal class StakingViewModel @Inject constructor( cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, userWalletProvider = Provider { userWallet }, appCurrencyProvider = Provider { appCurrency }, + balancesToShowProvider = Provider { balancesToShow }, ), ) }, From ecdeb1b82003a64493012dce2f0f2473f1d4117f Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 16:02:14 +0300 Subject: [PATCH 13/18] Updated on 2026-08-14 --- .../portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt | 2 +- .../markets/portfolio/impl/model/AddToPortfolioManager.kt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt index 80f3e39d33..e0bd92cbdf 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt @@ -62,7 +62,7 @@ internal class AddToPortfolioBSContentUMFactory( onNetworkSwitchClick = onNetworkSwitchClick, ).convert(value = token), isScanCardNotificationVisible = portfolioUIData.hasMissedDerivations, - continueButtonEnabled = portfolioUIData.addToPortfolioData.isUserChangedNetworks( + continueButtonEnabled = portfolioUIData.addToPortfolioData.isUserAddedNetworks( userWalletId = selectedWallet.walletId, ), onContinueButtonClick = { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioManager.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioManager.kt index be5ee4d9ed..74f85480e8 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioManager.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioManager.kt @@ -152,6 +152,10 @@ internal class AddToPortfolioManager @Inject constructor() { .orEmpty() } + fun isUserAddedNetworks(userWalletId: UserWalletId): Boolean { + return addedNetworks[userWalletId].orEmpty().isNotEmpty() + } + fun isUserChangedNetworks(userWalletId: UserWalletId): Boolean { return addedNetworks[userWalletId].orEmpty().isNotEmpty() || removedNetworks[userWalletId].orEmpty().isNotEmpty() From ba36b7d26dbbc7591f855b52c1d15268a43be06a Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 17:05:55 +0300 Subject: [PATCH 14/18] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 6df235a481..6ba358cced 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -224,6 +224,7 @@ internal class MainViewModel @Inject constructor( override fun onDismissBottomSheet() { listenToFlipsUseCase.changeUpdateEnabled(true) + router.pop() stateHolder.updateWithoutModalNotification() stateHolder.updateWithHiddenBalancesToast(true) } From dfcfce392ad2f185b03cdf712e8ec701dcc75eea Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 17:07:45 +0300 Subject: [PATCH 15/18] Updated on 2026-08-14 --- .../response/TokenMarketChartResponse.kt | 4 ++- .../markets/DefaultMarketsTokenRepository.kt | 29 +++++++++++++++++-- .../data/markets/MarketsBatchUpdateFetcher.kt | 20 +++++++++++++ .../analytics/MarketsDataAnalyticsEvent.kt | 13 +++++++++ .../markets/converters/TokenChartConverter.kt | 16 ++++++++-- 5 files changed, 76 insertions(+), 6 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketChartResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketChartResponse.kt index fbe921d20b..ea5be79096 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketChartResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketChartResponse.kt @@ -6,6 +6,8 @@ import java.math.BigDecimal @JsonClass(generateAdapter = true) data class TokenMarketChartResponse( + // There is a bug in the API, it returns null values. + // We need to filter them out. @Json(name = "prices") - val prices: Map, + val prices: Map, ) \ No newline at end of file diff --git a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt index 79576d75c3..20ca3a308c 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt @@ -103,6 +103,7 @@ internal class DefaultMarketsTokenRepository( val tokenMarketsUpdateFetcher = MarketsBatchUpdateFetcher( tangemTechApi = tangemTechApi, marketsApi = marketsApi, + analyticsEventHandler = analyticsEventHandler, onApiError = { analyticsEventHandler.send(MarketsDataAnalyticsEvent.List.Error.toEvent()) }, @@ -141,7 +142,19 @@ internal class DefaultMarketsTokenRepository( response.getOrThrow() } - return TokenChartConverter.convert(interval, result) + return TokenChartConverter.convert( + interval = interval, + value = result, + + // === Analytics === + onNullPresented = { + analyticsEventHandler.send( + MarketsDataAnalyticsEvent.ChartNullValuesError( + requestPath = "coins/history", + ), + ) + }, + ) } override suspend fun getChartPreview( @@ -163,7 +176,19 @@ internal class DefaultMarketsTokenRepository( ) } - return TokenChartConverter.convert(interval, chart) + return TokenChartConverter.convert( + interval = interval, + value = chart, + + // === Analytics === + onNullPresented = { + analyticsEventHandler.send( + MarketsDataAnalyticsEvent.ChartNullValuesError( + requestPath = "coins/history_preview", + ), + ) + }, + ) } override suspend fun getTokenInfo( diff --git a/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt b/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt index 18c7155b03..f1a92028fb 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt @@ -1,6 +1,8 @@ package com.tangem.data.markets +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.data.common.utils.retryOnError +import com.tangem.data.markets.analytics.MarketsDataAnalyticsEvent import com.tangem.data.markets.converters.TokenMarketChartsConverter import com.tangem.data.markets.converters.TokenQuotesShortConverter import com.tangem.data.markets.converters.toRequestParam @@ -22,6 +24,7 @@ import kotlinx.coroutines.launch internal class MarketsBatchUpdateFetcher( private val marketsApi: TangemTechMarketsApi, private val tangemTechApi: TangemTechApi, + private val analyticsEventHandler: AnalyticsEventHandler, private val onApiError: () -> Unit, ) : BatchUpdateFetcher, TokenMarketUpdateRequest> { @@ -52,6 +55,7 @@ internal class MarketsBatchUpdateFetcher( updateTasks.forEachIndexed { index, deferred -> launch { val res = deferred.await() + checkForNulls(res) val batchToUpdate = toUpdate[index] update { @@ -113,6 +117,22 @@ internal class MarketsBatchUpdateFetcher( ) } + private fun checkForNulls(response: TokenMarketChartListResponse) { + response.values.forEach { chart -> + chart.prices.forEach { (_, price) -> + if (price == null) { + analyticsEventHandler.send( + MarketsDataAnalyticsEvent.ChartNullValuesError( + requestPath = "coins/history_preview", + ), + ) + + return + } + } + } + } + private inline fun catchApiError(onError: () -> Unit, block: () -> T): T { return try { block() diff --git a/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt b/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt index b58f04cb33..1c131a3cd4 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt @@ -36,7 +36,20 @@ sealed interface MarketsDataAnalyticsEvent { } fun toEvent(): AnalyticsEvent = when (this) { + is ChartNullValuesError -> this is List -> this is Details -> this } + + data class ChartNullValuesError( + val requestPath: String, + ) : AnalyticsEvent( + category = "Markets / Chart", + event = "Data Error", + params = mapOf("Request path" to requestPath), + error = IllegalStateException( + "Chart data contains null values from the API", + ), + ), + MarketsDataAnalyticsEvent } \ No newline at end of file diff --git a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenChartConverter.kt b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenChartConverter.kt index 744e6fef48..a036ed9768 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenChartConverter.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenChartConverter.kt @@ -6,11 +6,21 @@ import com.tangem.domain.markets.TokenChart internal object TokenChartConverter { - fun convert(interval: PriceChangeInterval, value: TokenMarketChartResponse): TokenChart { + fun convert( + interval: PriceChangeInterval, + value: TokenMarketChartResponse, + onNullPresented: () -> Unit = {}, + ): TokenChart { + val points = value.prices.mapNotNull { p -> p.value?.let { p.key to it } }.toMap() + + if (points.size < points.values.size) { + onNullPresented() + } + return TokenChart( interval = interval, - priceY = value.prices.values.toList(), - timeStamps = value.prices.keys.toList(), + priceY = points.values.toList(), + timeStamps = points.keys.toList(), ) } } \ No newline at end of file From 3ebfda25132c580f7cdbe9f18ec228ebee73655e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 18:15:14 +0400 Subject: [PATCH 16/18] Updated on 2026-08-14 --- .../core/ui/components/buttons/common/TangemButton.kt | 5 ++++- .../ui/components/buttons/common/TangemButtonSize.kt | 9 ++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt index 3033f2221a..0dad99ad18 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.tangem.core.ui.components.ResizableText import com.tangem.core.ui.res.TangemTheme @@ -85,7 +86,9 @@ fun TangemButton( }, icon = { iconResId -> Icon( - modifier = Modifier.buttonContentSize(maxContentSize), + modifier = Modifier + .buttonContentSize(maxContentSize) + .padding(vertical = 2.dp), painter = painterResource(id = iconResId), tint = colors.contentColor(enabled = enabled).value, contentDescription = null, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt index bfae5cb313..961bca8869 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt @@ -33,9 +33,8 @@ internal fun TangemButtonSize.toHeightDp(): Dp = when (this) { @Composable @ReadOnlyComposable internal fun TangemButtonSize.toShape(): Shape = when (this) { - TangemButtonSize.Default, - TangemButtonSize.WideAction, - -> TangemTheme.shapes.roundedCornersMedium + TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersXMedium + TangemButtonSize.WideAction -> TangemTheme.shapes.roundedCornersMedium TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium @@ -64,8 +63,8 @@ internal fun TangemButtonSize.toContentPadding(icon: TangemButtonIconPosition): return when (this) { TangemButtonSize.Default -> PaddingValues( - top = TangemTheme.dimens.spacing14, - bottom = TangemTheme.dimens.spacing14, + top = TangemTheme.dimens.spacing12, + bottom = TangemTheme.dimens.spacing12, start = horizontalPadding.first, end = horizontalPadding.second, ) From 48f8f9e2ee8031ff0cd039ece07749bb42afd865 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 12:27:29 +0300 Subject: [PATCH 17/18] Updated on 2026-08-14 --- .../common/redux/legacy/LegacyMiddleware.kt | 29 +++++++++++++++++++ .../com/tangem/domain/redux/LegacyAction.kt | 2 ++ .../features/details/model/DetailsModel.kt | 7 +++++ 3 files changed, 38 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt index e4bf1086bd..2712e8375d 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.AmountType import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.redux.LegacyAction import com.tangem.domain.utils.convertToSdkAmount +import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.feedback.FeedbackEmail @@ -11,13 +12,23 @@ import com.tangem.tap.common.feedback.RateCanBeBetterEmail import com.tangem.tap.common.feedback.SendTransactionFailedEmail import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.saveIn +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.rekotlin.Middleware internal object LegacyMiddleware { + private val prepareDetailsScreenJobHolder = JobHolder() + val legacyMiddleware: Middleware = { _, _ -> { next -> { action -> @@ -85,6 +96,24 @@ internal object LegacyMiddleware { } } } + is LegacyAction.PrepareDetailsScreen -> { + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) + val walletsRepository = store.inject(DaggerGraphState::walletsRepository) + + userWalletsListManager.selectedUserWallet + .distinctUntilChanged() + .onEach { selectedUserWallet -> + store.dispatchWithMain( + DetailsAction.PrepareScreen( + scanResponse = selectedUserWallet.scanResponse, + shouldSaveUserWallets = walletsRepository.shouldSaveUserWalletsSync(), + ), + ) + } + .flowOn(Dispatchers.IO) + .launchIn(scope) + .saveIn(prepareDetailsScreenJobHolder) + } } next(action) } diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/LegacyAction.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/LegacyAction.kt index ab0183d09e..29003a8330 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/redux/LegacyAction.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/redux/LegacyAction.kt @@ -38,4 +38,6 @@ sealed interface LegacyAction : Action { val errorMessage: String, val scanResponse: ScanResponse, ) : LegacyAction + + data object PrepareDetailsScreen : LegacyAction } \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt index 884ee4f78a..9903c588e9 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt @@ -61,6 +61,9 @@ internal class DetailsModel @Inject constructor( ) init { + // Use to save compatibility with screens that using Redux states + bootstrapScreenState() + items .onEach(::updateState) .launchIn(modelScope) @@ -82,6 +85,10 @@ internal class DetailsModel @Inject constructor( ) } + private fun bootstrapScreenState() { + appStateHolder.dispatch(LegacyAction.PrepareDetailsScreen) + } + private fun sendFeedback() { modelScope.launch { val scanResponse = getSelectedWalletSyncUseCase().getOrNull()?.scanResponse From dd3139b717de9f462e70a882e3cc31d5d518e2bb Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Sep 2024 17:26:02 +0300 Subject: [PATCH 18/18] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 2 -- core/res/src/main/res/values-es/strings.xml | 2 -- core/res/src/main/res/values-fr/strings.xml | 2 -- core/res/src/main/res/values-ja/strings.xml | 2 -- core/res/src/main/res/values-ru/strings.xml | 23 +++++++++++++++++-- .../src/main/res/values-uk-rUA/strings.xml | 2 -- core/res/src/main/res/values/strings.xml | 2 -- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 177bb4faf9..05775e44ca 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -719,8 +719,6 @@ Block Tag Täglich - Minütlich - Alle ~ %1$s Sek. Epoche Ära Stunde diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 3116f0a193..25839b4722 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -718,8 +718,6 @@ Bloquear Día Cada día - Cada minuto - Cada ~%1$s segundo(s) Época Era Hora diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 2a75d83b48..2a9e104e07 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -718,8 +718,6 @@ Bloquer Jour Chaque jour - Chaque minute - Toute(s) les ~%1$sseconde(s) Époque Ère Heure diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 7f80905de9..a46b4b69aa 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -709,8 +709,6 @@ ブロック 毎日 - 毎分 - %1$s毎 エポック 時代 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 48a4894142..40de1c3f00 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -84,6 +84,7 @@ Перейти на %1$s Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. Отмена + Выберите действие Получить Вывести награду Закрыть @@ -656,6 +657,8 @@ Вы отправляете **%1$s**, включая комиссию сети %2$s Вы отправляете **%1$s** и %2$s Вы отправляете **%1$s** + Комиссия сети будет покрыта за счет использования %1$s энергии + Комиссия сети будет снижена за счет использования %1$s энергии включая комиссию сети в размере %1$s Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время %1$s — это монета в сети Tron. Чтобы рассчитать комиссию и совершить транзакцию, вам необходимо внести немного Tron (TRX) на свой адрес. @@ -667,10 +670,12 @@ Сумма для стейкинга должна быть не менее %s Согласно правилам сети, сумма стейкинга будет округлена до %1$sTRX. Забрать средства + Комиссия за стейкинг аккаунт Стейкинг аккаунт — это специальный счет, на котором хранятся застейканные токены SOL. Он создается при делегировании ваших токенов валидатору для участия в подтверждении транзакций и получении наград. За создание стейкинг аккаунта взимается небольшая комиссия, которая возвращается после завершения стейкинга. Процентная ставка Годовой процентный доход, который вы можете получить от участия в стейкинге. APR + Награда автоматически аккумулируется на вашем стейкинг балансе. Доступно Средння ставка вознаграждения Что такое Стейкинг? @@ -694,14 +699,26 @@ Переместить Нативный стейкинг Полученная награда отправится на ваш кошелек и сразу станет доступна для использования + Стейкайте безопасно и начинайте получать ваши награды. + Стейкайте безопасно и начинайте получать ежедневные награды. + Стейкайте безопасно и начинайте получать награды каждый час. + Стейкайте безопасно и начинайте получать ежемесячные награды. Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждый день. Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждый час. Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждый месяц. Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждую неделю. + Стейкайте безопасно и начинайте получать еженедельные награды. Получите награду за стейкинг + Стейкинг временно недоступен из-за проблем в сети. Пожалуйста, попробуйте позже. + Стейкинг в сети %1$s с новым валидатором автоматически переведет ваши текущие застейканные средства на него. + Реинвестируйте свои заработанные награды в вашу застейканную сумму, увеличивая потенциальный доход Разблокируйте свои средства, чтобы вывести их из стейкинга. Разблокировка займёт %s. + Ваши средства будут доступны для использования после 21-дневного периода отзыва. Награда будет выведена вместе с вашими выводими средствами. Ваши средства будут доступны после %s периода отзыва. Вы можете вывести свои средства из стейкинга, они будут доступны для использования незамедлительно. + Стейкинг в сети Tron с новым валидатором автоматически переведет ваши текущие застейканные средства на него. + Подготовка + Доступно для вывода Сменить валидатора Застейкать вознаграждения Отозвать @@ -711,8 +728,6 @@ Блок День Каждый день - Каждую минуту - Каждые ~%1$s сек Эпоха Эра Час @@ -721,12 +736,15 @@ Вознаграждения Стейкинг закрыт Застейкать еще + Вы стейкаете %1$s и будете получать %2$s ежегодно Нажмите для разблокировки Нажмите для вывода Застейкать %s Вывести %s + Транзакция обрабатывается! В настоящее время идет проверка в блокчейне. Это может занять несколько минут. Отзыв Разблокировать + Разблокировка Вывод из стейкинга Завершение стейкинга Завершение стейкинга %s @@ -735,6 +753,7 @@ Проголосовать Голосование заблокировано Вывод + Застейканные средства Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. Революционный аппаратный кошелек До трех карт с одним кошельком diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 416b7f49ec..15a063ac84 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -731,8 +731,6 @@ Блок День Щодня - Щохвилини - Кожні ~%1$sсек Епоха Ера Година diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index c4e1616a0d..93fba71bd2 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -718,8 +718,6 @@ Block Day Daily - Each minute - Each ~%1$ssec Epoch Era Hour