diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index 4434311a95..f49789df71 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -8,6 +8,8 @@ import com.tangem.domain.demo.DemoConfig import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase +import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase +import com.tangem.tap.domain.sdk.TangemSdkManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -69,4 +71,10 @@ internal object CardDomainModule { ): GetExtendedPublicKeyForCurrencyUseCase { return GetExtendedPublicKeyForCurrencyUseCase(derivationsRepository) } + + @Provides + @ViewModelScoped + fun provideDeleteSavedAccessCodesUseCase(tangemSdkManager: TangemSdkManager): DeleteSavedAccessCodesUseCase { + return DefaultDeleteSavedAccessCodesUseCase(tangemSdkManager) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index da46031149..22502ca5c5 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -7,8 +7,8 @@ import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository import com.tangem.domain.settings.* import com.tangem.domain.settings.repositories.AppRatingRepository +import com.tangem.domain.settings.repositories.PromoSettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository -import com.tangem.domain.settings.repositories.SwapPromoRepository import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository import dagger.Module @@ -108,17 +108,25 @@ internal object SettingsDomainModule { @Provides @ViewModelScoped fun provideShouldShowSwapPromoWalletUseCase( - swapPromoRepository: SwapPromoRepository, + promoSettingsRepository: PromoSettingsRepository, ): ShouldShowSwapPromoWalletUseCase { - return ShouldShowSwapPromoWalletUseCase(swapPromoRepository) + return ShouldShowSwapPromoWalletUseCase(promoSettingsRepository) + } + + @Provides + @ViewModelScoped + fun provideShouldShowTravalaPromoWalletUseCase( + promoSettingsRepository: PromoSettingsRepository, + ): ShouldShowTravalaPromoWalletUseCase { + return ShouldShowTravalaPromoWalletUseCase(promoSettingsRepository) } @Provides @ViewModelScoped fun provideShouldShowSwapPromoTokenUseCase( - swapPromoRepository: SwapPromoRepository, + promoSettingsRepository: PromoSettingsRepository, ): ShouldShowSwapPromoTokenUseCase { - return ShouldShowSwapPromoTokenUseCase(swapPromoRepository) + return ShouldShowSwapPromoTokenUseCase(promoSettingsRepository) } @Provides diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 6821d4a992..91c7d4aadb 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -346,4 +346,12 @@ internal object TokensDomainModule { ): RunPolkadotAccountHealthCheckUseCase { return RunPolkadotAccountHealthCheckUseCase(repository) } + + @Provides + @ViewModelScoped + fun provideGetNetworkStatusesUseCase(networksRepository: NetworksRepository): GetNetworkAddressesUseCase { + return GetNetworkAddressesUseCase( + networksRepository = networksRepository, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt new file mode 100644 index 0000000000..8834604935 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt @@ -0,0 +1,22 @@ +package com.tangem.tap.domain.card + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.domain.card.DeleteSavedAccessCodesUseCase +import com.tangem.tap.domain.sdk.TangemSdkManager + +internal class DefaultDeleteSavedAccessCodesUseCase( + private val tangemSdkManager: TangemSdkManager, +) : DeleteSavedAccessCodesUseCase { + + override suspend fun invoke(cardId: String): Either { + tangemSdkManager.deleteSavedUserCodes(setOf(cardId)) + .doOnFailure { return it.left() } + .doOnSuccess { return Unit.right() } + + return Unit.right() + } +} \ No newline at end of file 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 6c4c24f485..6840975e44 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 @@ -14,6 +14,7 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.ListenToFlipsUseCase import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase +import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager @@ -39,6 +40,7 @@ internal class MainViewModel @Inject constructor( private val blockchainSDKFactory: BlockchainSDKFactory, private val userWalletsListManager: UserWalletsListManager, private val walletManagersFacade: WalletManagersFacade, + private val sendFeatureToggles: SendFeatureToggles, private val dispatchers: CoroutineDispatcherProvider, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel(), MainIntents { @@ -61,6 +63,7 @@ internal class MainViewModel @Inject constructor( viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() } updateAppCurrencies() + updateSendFeatureToggle() observeFlips() displayBalancesHidingStatusToast() displayHiddenBalancesModalNotification() @@ -108,6 +111,12 @@ internal class MainViewModel @Inject constructor( } } + private fun updateSendFeatureToggle() { + viewModelScope.launch(dispatchers.main) { + sendFeatureToggles.fetchNewSendEnabled() + } + } + private fun observeFlips() { listenToFlipsUseCase().launchIn(viewModelScope) } diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index eb8ef3b54b..b77078a426 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -101,6 +101,7 @@ sealed class AnalyticsParam { data object Min : FeeType("Min") data object Normal : FeeType("Normal") data object Max : FeeType("Max") + data object Custom : FeeType("Custom") companion object { fun fromString(feeType: String): FeeType { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/PromotionInfoResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/PromotionInfoResponse.kt index fa9140eea1..668a77c321 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/PromotionInfoResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/PromotionInfoResponse.kt @@ -13,6 +13,7 @@ data class PromotionInfoResponse( data class BannerState( @Json(name = "timeline") val timeline: Timeline, @Json(name = "status") val status: String, + @Json(name = "link") val link: String?, ) @JsonClass(generateAdapter = true) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index 0a5dcf3fd9..eb9e2a434c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -10,6 +10,7 @@ import retrofit2.http.* * [REDACTED_AUTHOR] */ +@Suppress("TooManyFunctions") interface TangemTechApi { @GET("coins") @@ -126,4 +127,7 @@ interface TangemTechApi { @Header("card_id") cardId: String, @Path("account_id") accountId: Int, ): ApiResponse + + @GET("features") + suspend fun getFeatures(): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/FeaturesResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/FeaturesResponse.kt new file mode 100644 index 0000000000..2d72021cd0 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/FeaturesResponse.kt @@ -0,0 +1,7 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +data class FeaturesResponse( + @Json(name = "send") val isNewSendEnabled: Boolean, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 4dfd9d6966..cfafab3c05 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -61,6 +61,10 @@ object PreferencesKeys { booleanPreferencesKey(name = "isTokenSwapPromoChangellyShown") } + val IS_WALLET_TRAVALA_PROMO_SHOWN_KEY by lazy { + booleanPreferencesKey(name = "isWalletTravalaPromoShown") + } + val FEATURE_TOGGLES_KEY by lazy { stringPreferencesKey(name = "featureToggles") } val WAS_TWINS_ONBOARDING_SHOWN by lazy { booleanPreferencesKey(name = "twinsOnboardingShown") } diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index d3be4f5db5..d91a3988e8 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -9,7 +9,7 @@ }, { "name": "REDESIGNED_SEND_SCREEN_ENABLED", - "version": "5.9.1" + "version": "5.10.0" }, { "name": "LOCAL_USER_LOGS_ENABLED", diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 7d27d1a437..53f6a46e4e 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1,722 +1,715 @@ - Добавить токен - Валюты - Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств. - Обратиться в поддержку - Попробовать снова - Эта функция недоступна в демонстрационном режиме - Причина: %s - Не могу отправить транзакцию - Выбранный кошелёк не поддерживает сеть %1$s - Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. - Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. - Спасибо за ваш отзыв. Мы ответим в кратчайшие сроки. - Ваши предложения отправлены - Пожалуйста, попробуйте приложить карту в точности, как показано на анимации, или запросите поддержку. - У вас возникли трудности со сканированием карты? - Эта карта не предназначена для работы с этим приложением - Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. - Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem - Включите биометрическую аутентификацию - Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. - При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены из приложения. - Сохранение кода доступа - Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. - Cохранение кошелька - Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. - Тёмная - Светлая - Как в системе - При выборе настройки как в системе приложение будет использовать тему в соответствии с настройками вашего устройства - Системная - Тема - Настройки приложения - Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" - Больше не показывать - Понятно - Балансы скрыты - Пожалуйста, отсканируйте карту - Пожалуйста, попробуйте снова через 30 секунд или отсканируйте карту - Слишком много попыток - Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона. - Начать резервное копирование - - %d карта - %d карты - %d карт - %d карт - - Отключите эту опцию, если не хотите, чтобы эта карта использовалась для сброса кодов доступа на другие карты этого кошелька. Обратите внимание, сброс кода также не будет доступен на этой карте. - Использовать эту карту для сброса кода доступа на других картах в этом кошельке - Восстановление кода доступа - Сбросить - Вы уверены, что хотите это сделать? - Смена кода доступа - Код доступа будет изменен только на данной карте - Заводские настройки - Тип безопасности - Настройки карты - Помимо сетевой комиссий, сеть Cardano взимает %1$s ADA при транзакции с токеном %2$s - Требования к транзакции Cardano - Чтобы совершить транзакцию %1$s, внесите некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA (рекомендуется 5 ADA) - Недостаточно ADA для транзакции - Вы должны поддерживать некоторое количество ADA, поскольку у вас на балансе есть токены в сети Cardano - Недостаточно ADA - Принять - Доступ запрещен - Применить - Одобрение - Внимание - Баланс: %s - Баланс - биометрическую аутентификацию - биометрией - Купить - Перейти на %1$s - Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. - Отмена - Закрыть - Продолжить - Копировать - Скопировать адрес - Создать - Удалить - Отключено - Отключить - Готово - Включить - Включено - Ошибка - Обозреватель - Посмотреть историю транзакций - Обозреватель - Комиссия - Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s - Свое - Быстро - По рынку - Медленно - Скорость и комиссия - Получить адреса - К провайдеру - Импортировать - Заблокирован - Основная сеть - Сетевая комиссия - Далее - Нет - Нет адреса - OK - Основная карта - Парольная фраза - Вставить - Подробнее - Получить - Отклонить - Перезагрузить - Переименовать - Повторить - Сохранить изменения - Искать - Поиск токенов - Seed-фраза - Выберите действие - Продать - Отправить - Сервер недоступен, повторите попытку позднее - Поделиться - Подписать - Подписать и отправить - Начать - Отправить - Успешно - Поддержка - Обмен - условия участия - Ошибка транзакции - Транзакции - Перевод - Я понял - Произошла ошибка. Пожалуйста, попробуйте снова. - Недоступно - Предупреждение - Да - Адрес контракта скопирован! - Доступные сети - Добавить токен - Адрес контракта - Адрес контракта некорректен - Пожалуйста, выберите сеть - Десятичное число должно быть действительным целым числом, до %li - Своя деривация - Например m/00\'/0000\'/0\'/0/0 - Введите свою деривацию - Знаков после запятой - Путь деривации - По умолчанию - Деривация по BIP44 - Введенный путь деривации некорректен - Например, USD Coin - Название токена - Не выбрано - Сеть - Сеть - Вы можете добавить токен в ручную, если он не поддерживается Tangem - Например, USDC - Символ - Символ токена - Этот токен/сеть уже находится в вашем списке - Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить - Остерегайтесь мошеннических токенов, они могут ничего не стоить - Токены могут быть созданы кем угодно - Чат - Код доступа - Перед сканированием карты вам нужно будет ввести правильный код доступа. - Задержка сканирования - Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. - Пароль - Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. - Реферальная программа - Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы - %s хэшей - Номер карты - Обратиться в поддержку - Добавить еще карты - Валюта приложения - Скрывать балансы жестом переворота - Эмитент - Подписано - Если вы забудете код, то потеряете доступ к своим средствам. Восстановление кода невозможно. - Подробности - Проверьте подключение с интернетом или переключитесь на другую сеть - Условия использования - Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. - Мои токены - У вас нет добавленных токенов. Добавьте токены для обмена - Недоступен для обмена с %s - Предоставлено - Статус - Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами - Выберите провайдера - Произошла ошибка. Код: %s - К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) - Выбранный провайдер недоступен для обмена. Попробуйте позже. (Код: %s) - В данный момент обмен невозможен. Попробуйте позже. (Код: %s) - Курс обмена - Обмен через %s - Чтобы вернуть ваши деньги, посетите сайт провайдера - Операция не выполнена провайдером - Посетите сайт провайдера для проверки - Провайдер запрашивает прохождение верификации - Отменен - Подтверждено - Подтверждение - Подтверждение... - Обменяно - Обмен - Обмен... - Неудачно - Депозит получен - Ожидание депозита - Ожидаем пополнения... - Возвращено - Отправляем - Отправка средств... - Отправлено - Данные провайдера. Сумма к получению может измениться в зависимости от рыночных условий. - Статус обмена - Требуется верификация - Список токенов в вашем кошельке - Получение наилучших курсов... - Плавающая ставка - Пользуясь сервисом, вы соглашаетесь с %s - Пользуясь сервисом, вы соглашаетесь с %1$s и %2$s - Больше провайдеров на подходе.\nСледите за обновлениями! - Политикой конфиденциальности - Провайдер - Лучший курс - Доступно до %s - Доступно с %s - Недоступно для этой пары - Требуется разрешение - Условиями использования - Токены не найдены. Пожалуйста, попробуйте другой запрос - ID транзакции скопирован - Информация ниже не является обязательной. Вы можете стереть её, если хотите. - Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. - Скажите, пожалуйста, какая у вас карта? - Привет, команда поддержки, - Пожалуйста, расскажите нам больше о вашей проблеме. Каждая маленькая деталь может помочь. - Мои предложения - Не могу отсканировать карту - Обращение в поддержку - Обращение в поддержку Tangem - Не могу отправить транзакцию - Купить - Сканировать - Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции - Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции - Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции - Приложите, чтобы отсканировать - Приложите, чтобы подписать - Приложите карту - Вы обновили данные биометрии, отсканируйте свою карту для входа - Чтобы начать отслеживать свои криптоактивы и транзакции, добавьте токены - Управление токенами - Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту - Отсканируйте карту - Обменивайте свои токены с %1$s комиссии провайдера через Changelly с %2$s по %3$s февраля. - Обмен с Changelly, %s комиссии - Токены - Добавить - Изменить - Рыночная капитализация - Основная сеть - Не основной или основной блокчейн, на котором размещен токен - Не основные сети - Выберите сети - Кошелек - Не удалось найти этот токен, вы можете добавить его вручную. - - %1$d из %2$d кошелька - %1$d из %2$d кошельков - %1$d из %2$d кошельков - %1$d из %2$d кошельков - - например Bitcoin - Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление. - Голосовать - Выберите кошелек - Кошелёк не поддерживает более одной сети - Вам необходимо установить единый код доступа для защиты всех ваших карт - Защита - Позже вы сможете установить индивидуальный код доступа для каждой карты - Персонализация - Код доступа можно восстановить с помощью привязанной карты. Не храните все карты в одном месте. - Восстановление - Выберите любое слово, фразу или число в качестве кода доступа - Создайте код доступа - Введите код доступа еще раз, чтобы избежать ошибки - Повторно введите код доступа - Код доступа должен состоять не менее чем из 4 символов. - Введенные коды доступа не совпадают - Необходимо повторить операцию, при этом карта будет сброшена к заводским настройкам - Ошибка активации - Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? - Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. - Парольная фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. - Добавить резервную карту - Сканировать карту #%d - Создать резервную копию - Сканировать основную карту - Перейти к моему кошельку - Завершение бэкапа - Получить криптовалюту - Сканировать основную карту - Пропустить - Как это работает? - Давайте сгенерируем все ключи на вашей карте и создадим безопасный кошелек - Создать кошелек - Создать кошелек - Другие опции - Ваши ключи будут надежно сгенерированы внутри карты. Никакой seed-фразы, а это значит, что никто не может экспортировать или украсть ее. - Cоздавайте ключи приватно - Ваша карта активирована и готова к использованию - Успешно! - В этом случае вам будет необходимо начать процесс заново. - Вы хотите выйти из процесса активации? - Подготовка - Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Если на нем есть средства, пожалуйста сначала выведите их, а затем сделайте сброс до заводских настроек и используйте как резервную. - Резервная копия - Прочитать о seed-фразе - - - Запишите эти %d слова в порядке, указанном ниже, и сохраните их в надежном месте. - Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. - Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. - - Ваша seed-фраза - - - %d слова - %d слов - %d слов - - Чтобы импортировать кошелек, введите seed-фразу в поле ниже - Создать seed-фразу - Импорт кошелька - Seed-фраза — это набор слов, который дает возможность восстановить кошелек. В отличие от ключей, сгенерированных картой, seed-фраза не защищена и может быть скопирована и украдена. Используйте этот вариант на свой страх и риск. - Использовать seed-фразу - Неверная seed-фраза. Пожалуйста, проверьте порядок слов. - Неверная seed-фраза. Пожалуйста, проверьте орфографию. - Устаревший - Чтобы проверить, правильно ли вы записали seed-фразу, введите 2-е, 7-е и 11-е слова - Итак, проверим - Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. - Вы можете добавить еще одну карту или завершить процесс резервного копирования - Подготовьте резервную карту с номером %s - Отсканируйте основную карту, чтобы начать процесс резервного копирования. - Подготовьте основную карту с номером %s - Ваша карта настроена и готова к использованию. - Добавлено максимальное количество карт. Завершите процесс резервного копирования. - Активация карты - Резервная карта #%d - Нет резервных карт - Добавлена ​​одна резервная карта - Подготовьте свою карту - Добавлены две резервные карты - Пополните кошелек на любую сумму, чтобы начать пользоваться картой - Пополните кошелек более чем на %1$s %2$s, чтобы начать пользоваться картой - Купить криптовалюту - Показать адрес кошелька - Активация кошелька - Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. - Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала - Вы можете сделать резервную копию своих ключей на одной или двух других пустых картах Wallet. - Код доступа можно восстановить с помощью одной из резервных карт. - Все резервные карты являются полнофункциональными и содержат одинаковые ключи. - Вы сможете установить код доступа для защиты своих кошельков. - Резервная копия карты - Восстановление кода доступа - Идентичные карты - Код доступа - Группы - По балансу - Сортировка токенов - Список - Выбрать из галереи - Нравится наше приложение? - %1$s (%2$s) в сети %3$s - Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств. - Участвовать - Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. - Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. - Грядущие выплаты - Ваши друзья купили - Меньше - Больше - Нет грядущих выплат - - за %d кошелек - за %d кошелька - за %d кошельков - за %d кошельков - - Получите ^^%1$s^^ на ваш адрес в сети %2$s %3$s ^^спустя 30 дней^^ за каждый кошелек, который купит ваш друг - Вы - Получит - при покупке кошелька на сайте tangem.com - %s скидку - Ваш друг - Персональный код скопирован! - Ваш персональный код - Купи Tangem Wallet со скидкой!\n%s - Приведи друга в Tangem - Вы приняли - Нажимая на эту кнопку, вы принимаете - в реферальной программе - - %d кошелек - %d кошелька - %d кошельков - %d кошельков - - Сбросить карту - Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку - Я понимаю, что не смогу этой картой восстановить пароль на остальных картах этого кошелька, если я его забуду - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать данную карту для восстановления кода доступа. - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек. - У вас есть карта банка другой страны, а также вид на жительство или регистрация вне РФ? - Карты банков РФ в данный момент не принимаются - Войдите в приложение и следите за своим балансом без сканирования карты - Доступ в приложение - Использовать биометрию - Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты - Код доступа - Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков - Включите биометрическую аутентификацию - Вы хотите использовать биометрию? - Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта - Сканировать - Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. - Приготовьте свою карту - Уже содержится в введенном адресе - Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. - Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? - Причина: %1$s\nКод: %2$s - Транзакция не выполнена - Сумма - %1$s, %2$s - Адрес - Код назначения - Введите адрес - Адрес совпадает с адресом кошелька - Недопустимый Tag. Он не будет добавлен в транзакцию. - Недопустимый Memo. Он не будет добавлен в транзакцию. - Tag - Memo - Включая комиссию - Низкая - Нормальная - Приоритетная - Проверьте своё интернет соединение - Информация о комиссии сети недоступна - Из - Лимит газа - Это максимальное количество газа, которое будет потрачено на выполнение транзакции или контракта. Лимит газа предотвращает неожиданные или неограниченные расходы при выполнении транзакции. - Цена газа - Это стоимость, которую вы готовы заплатить за каждую единицу газа. Чем выше цена газа, тем быстрее ваша транзакция будет обработана. - Всё - Максимальная сумма - Комиссия не превысит - Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение. - Допустим ввод только цифр - Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии - Покрытие сетевой комиссии - Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса - Недостаточно средств - Оставить %s - Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе. - Экзистенциальный депозит - Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. - Установлена высокая комиссия - Ввиду особенности сети Tezos комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %s. - Комиссия повышена - Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению - Недопустимая сумма - Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %2$s. - Адрес получателя не активирован. \nПожалуйста, измените сумму отправки, чтобы продолжить. - Сумма отправки не может быть менее %s - Уменьшить на %s - Уменьшить до %s - Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции - Возможны задержки по транзакции - Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. - Лимит транзакции - Опционально - Пожалуйста, совместите свой QR-код с квадратом, чтобы отсканировать его. Убедитесь, что вы сканируете адрес в сети %s. - Последние - Получатель - Неверный адрес - Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов - Отправить - Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. - Мои кошельки - Способ измерения комиссии за биткоин-транзакцию. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый виртуальный байт в транзакции. Чем выше число, тем быстрее будет обработана транзакция майнерами. - Сатоши / вбайт - Отправка - Нажмите на любое поле, чтобы изменить его - Отправка %s - Вы отправляете %1$s, включая комиссию сети %2$s - Отправка %s - Всего - %1$s и %2$s будет отправлено - ≈ %1$s (вкл. комиссию: %2$s) - %s будет отправлено - Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время - Неверный адрес - Транзакция отправлена - Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. - Революционный аппаратный кошелек - До трех карт с одним кошельком - Все ключи в безопасности - Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте - Тысячи криптовалют - Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. - Кошелек для каждого - Встречайте Tangem - Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах - Поддержка Web 3.0 - В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя - В сумму включена комиссия провайдера сервиса. - Комиссии - Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен. - Подтвердить - Вы отправляете - Дать разрешение - Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму. - Недостаточно средств - Сумма отправки будет уменьшена для покрытия выбранного уровня комиссии - Подтвердить - Текущая транзакция - Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена. - Дать разрешение - Укажите лимит доступа к выбранному токену - Количество %s - Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s - Безлимитно - В процессе - Обменять - Вы получите - Выберите токен - не доступен - Балансы скрыты - Балансы показаны - Отменить - Выбранная операция в данный момент недоступна. Попробуйте позже. - В данный момент покупка монеты %s недоступна. Следите за нашими обновлениями. - У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. - У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. - В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. - Продажа средств станет доступной после завершения транзакции(-ий) в сети %s - Отправка средств станет доступной после завершения транзакции(-ий) в сети %s - В данный момент продажа %s недоступна. Следите за нашими обновлениями. - Выберите адрес - Сгенерировать XPUB - Скрыть - Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. - Скрыть %s - Скрыть токен - %1$s токен в сети %%image%% %2$s - Токен в сети %%image%% %1$s - Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. - Невозможно скрыть %s - Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля. - Обмен с Changelly, %s комиссии - Обменять - контракт: %s - У вас еще нет транзакций - Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию. - Несколько адресов - История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе. - Операция - от: %s - на: %s - Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d - Вы отсканировали не ту twin-карту. Пожалуйста, попробуйте отсканировать другую - Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька. - Один кошелек. Две карты. - Сканировать карту #%s - Создание кошелька - Отсканируйте twin-карту #%s - Подготовка карты - Tangem Twin - Это действие необратимо. У вас не будет доступа к старому кошельку. - Приложите twin-карту с номером %s и не убирайте до окончания операции - Используйте %s или отсканируйте карту, чтобы получить доступ к своему кошельку - Добавить новый кошелек - Вы уверены, что хотите удалить этот кошелек? - Произошла ошибка, пожалуйста, отсканируйте свою карту для входа - Этот кошелек уже был сохранен, вы можете добавить другой - Имя кошелька - Переименование кошелька - Разблокировать все - Разблокировать все с %s - Блокчейн недоступен. Попробуйте позже. - Отсканируйте карту - Запрос на подпись сообщения.\n\n%s - Dapp %1$s, запрос на\nподпись транзакции с BNB.\n\n%2$s - Торговый ордер на %1$s\nЦена: %2$s\nСумма к получению: %3$s\nСумма к оплате: %4$s - Детали транзакции:\nОт: %1$s\nК: %2$s\nСумма: %3$s - Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код - Запрос на создание транзакции для %1$s\n%2$s\n\nСумма: %3$s\nКомиссия: %4$s\nВсего: %5$s\nБаланс: %6$s - Невозможно отправить транзакцию. Недостаточно средств. - Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. - Не все токены добавлены в ваш список. Пожалуйста, добавьте их в начале, а потом попробуйте снова. Недостающие токены: \n - Не удалось подписать сообщение.\nПожалуйста, попробуйте еще раз - Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже. - Запрос на подключение через WalletConnect содержит неподдерживаемые блокчеины. Неподдерживаемые блокчеины:\n - Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. - Произошла непредвиденная ошибка. Сообщение ошибки: %s Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. - Неверная карта выбрана в приложении Tangem - Не удалось создать транзакцию из данных Dapp. Код: %s - Произошла непредвиденная ошибка. Код ошибки: %d Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. - Нет открытых сессий WalletConnect - Упс. Нет сессий. - Не удалось создать пару WalletConnect: %1$s - Вставить из буфера обмена - Сообщение для %1$s:\n%2$s - Запрос на открытие сессии для\n%1$s\n\nСЕТЬ: %2$s\n\nURL: %3$s - Операция не может быть завершена.\n\nВы уже установили сеанс WalletConnect с этими параметрами. - Сканировать новый код - Эту карту нельзя использовать с WalletConnect. - Сеть не поддерживается. Пожалуйста, выберите другую сеть. - Выберите сеть - Сессии WalletConnect - Подключение к dApps - WalletConnect - Рыночная цена %s - за 24 часа - Сеть %s - Адрес скопирован в буфер обмена - Нет соединения с интернетом - Tangem - Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку - Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян. - Ошибка активации - Можно лучше - Нравится - Понятно! - Очень круто! - Обновить - Вы находитесь в режиме демо - Демо режим включен - Отсканированная вами карта является картой разработчика. Не используйте ее для создания своего кошелька. - Не для пользователя! - Cеть %1$s использует концепцию экзистенциального депозита. Если баланс вашего счета будет ниже %2$s, то он будет деактивирован, а средства на счете уничтожены. - Для работы с сетью необходим депозит - Обмен будет доступен после завершения %s транзакции - У вас есть активная транзакция - Разрешение обмена в процессе и будет скоро завершено - Разрешение в процессе - Минимальная сумма обмена - %1$s. Пожалуйста, убедитесь, что остаток после обмена также не будет меньше %2$s. - У вас в списке нет монет доступных для обмена с %s - Нет доступных для обмена токенов - Чтобы совершить транзакцию, вам необходимо внести немного %1$s %2$s - Невозможно покрыть комиссию %s - Сумма получения не может быть менее %s - Cервис временно недоступен - Сумма для обмена должна быть не более %s - Сумма для обмена должна быть не менее %s - Пожалуйста, измените сумму для обмена - Возможно, данная карта - образец или подделка - Ошибка проверки подлинности - Ассоциировать - Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%1$s %2$s - Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять - Ассоциируете свой токен - Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена - На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. - Малое количество подписей - Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. - - Используйте вашу карту, чтобы получить адрес для %d сети - Используйте вашу карту, чтобы получить адреса для %d сетей - Используйте вашу карту, чтобы получить адреса для %d сетей - Используйте вашу карту, чтобы получить адреса для %d сетей - - Некоторые адреса отсутствуют - В данный момент сеть недоступна. Пожалуйста, попробуйте позже. - Сеть недоступна - Пополните ваш кошелек - Ваш кошелек не имеет резервной копии. Проведите эту процедуру сейчас, чтобы защитить ваши активы. - Резервная копия отсутствует - Эта карта ранее использовалась для подписи транзакций. Если она получена от ненадежного источника, рассмотрите возможность вывода своих средств. Если это ваша карта, дополнительных действий не требуется. - Карта уже подписывала транзакции - Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше - Нравится Tangem? - Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его - Необходима плата за аренду сети - %1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети. - Недостаточно %1$s для оплаты комиссии сети - Транзакция в обработке - Сеть Солана испытывает высокую нагрузку. Если Ваша транзакция не прошла в течение 2 минут, повторите её отправку. - Оповещение сети Солана - Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату. - Некоторые сети в настоящее время недоступны. Пожалуйста, повторите попытку позже. - Некоторые сети недоступны - Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки. - Только для целей тестирования - Отказаться - Вы не закончили резервное копирование. Хотите продолжить? - Да, возобновить - Отказаться - Если сейчас отказаться, то придётся сбрасывать карты до заводских настроек, чтобы начать заново - Возобновить резервное копирование - Это необратимое действие - Войти с %s - Сканировать карту - Используйте %s или отсканируйте карту для входа в приложение - C возвращением! + Добавить токен + Валюты + Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств. + Обратиться в поддержку + Эта функция недоступна в демонстрационном режиме + Причина: %s + Не могу отправить транзакцию + Выбранный кошелёк не поддерживает сеть %1$s + Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен. + Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки. + У вас возникли трудности со сканированием карты? + Эта карта не предназначена для работы с этим приложением + Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться. + Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem + Включите биометрическую аутентификацию + Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. + При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены из приложения. + Сохранение кода доступа + Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. + Cохранение кошелька + Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. + Тёмная + Светлая + Как в системе + Тема + Настройки приложения + Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\" + Больше не показывать + Понятно + Балансы скрыты + Пожалуйста, отсканируйте карту + Пожалуйста, попробуйте снова через 30 секунд или отсканируйте карту + Слишком много попыток + Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона. + Начать резервное копирование + + %d карта + %d карты + %d карт + %d карт + + Отключите эту опцию, если не хотите, чтобы эта карта использовалась для сброса кодов доступа на другие карты этого кошелька. Обратите внимание, сброс кода также не будет доступен на этой карте. + Использовать эту карту для сброса кода доступа на других картах в этом кошельке + Восстановление кода доступа + Сбросить + Вы уверены, что хотите это сделать? + Смена кода доступа + Код доступа будет изменен только на данной карте + Заводские настройки + Тип безопасности + Настройки карты + Помимо сетевой комиссий, сеть Cardano взимает %1$s ADA при транзакции с токеном %2$s + Требования к транзакции Cardano + Чтобы совершить транзакцию %1$s, внесите некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA (рекомендуется 5 ADA) + Недостаточно ADA для транзакции + Вы должны поддерживать некоторое количество ADA, поскольку у вас на балансе есть токены в сети Cardano + Недостаточно ADA + Принять + Доступ запрещен + Применить + Одобрение + Внимание + Баланс: %s + Баланс + биометрическую аутентификацию + биометрией + Купить + Перейти на %1$s + Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. + Отмена + Закрыть + Продолжить + Копировать + Скопировать адрес + Создать + Удалить + Отключено + Готово + Включить + Включено + Ошибка + Обозреватель + Посмотреть историю транзакций + Обозреватель + Комиссия + Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s + Свое + Быстро + По рынку + Медленно + Скорость и комиссия + Получить адреса + К провайдеру + Импортировать + Заблокирован + Основная сеть + Сетевая комиссия + Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии + Далее + Нет + Нет адреса + OK + Основная карта + Парольная фраза + Вставить + Подробнее + Получить + Отклонить + Перезагрузить + Переименовать + Сохранить изменения + Искать + Поиск токенов + Seed-фраза + Выберите действие + Продать + Отправить + Сервер недоступен, повторите попытку позднее + Поделиться + Подписать + Подписать и отправить + Начать + Отправить + Успешно + Поддержка + Обмен + условия участия + Ошибка транзакции + Транзакции + Перевод + Я понял + Произошла ошибка. Пожалуйста, попробуйте снова. + Недоступно + Да + Адрес контракта скопирован! + Доступные сети + Добавить токен + Адрес контракта + Адрес контракта некорректен + Пожалуйста, выберите сеть + Десятичное число должно быть действительным целым числом, до %li + Своя деривация + Например m/00\'/0000\'/0\'/0/0 + Введите свою деривацию + Знаков после запятой + Путь деривации + По умолчанию + Деривация по BIP44 + Введенный путь деривации некорректен + Например, USD Coin + Название токена + Не выбрано + Сеть + Сеть + Вы можете добавить токен в ручную, если он не поддерживается Tangem + Например, USDC + Символ + Символ токена + Этот токен/сеть уже находится в вашем списке + Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить + Остерегайтесь мошеннических токенов, они могут ничего не стоить + Токены могут быть созданы кем угодно + Чат + Код доступа + Перед сканированием карты вам нужно будет ввести правильный код доступа. + Задержка сканирования + Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. + Пароль + Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. + Реферальная программа + Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы + %s хэшей + Номер карты + Обратиться в поддержку + Добавить еще карты + Валюта приложения + Скрывать балансы жестом переворота + Эмитент + Подписано + Подробности + Проверьте подключение с интернетом или переключитесь на другую сеть + Условия использования + Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. + Мои токены + У вас нет добавленных токенов. Добавьте токены для обмена + Недоступен для обмена с %s + Предоставлено + Статус + Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами + Выберите провайдера + Произошла ошибка. Код: %s + К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s) + Выбранный провайдер недоступен для обмена. Попробуйте позже. (Код: %s) + В данный момент обмен невозможен. Попробуйте позже. (Код: %s) + Курс обмена + Обмен через %s + Чтобы вернуть ваши деньги, посетите сайт провайдера + Операция не выполнена провайдером + Посетите сайт провайдера для проверки + Провайдер запрашивает прохождение верификации + Отменен + Подтверждено + Подтверждение + Подтверждение... + Обменяно + Обмен + Обмен... + Неудачно + Депозит получен + Ожидание депозита + Ожидаем пополнения... + Возвращено + Отправляем + Отправка средств... + Отправлено + Данные провайдера. Сумма к получению может измениться в зависимости от рыночных условий. + Статус обмена + Требуется верификация + Список токенов в вашем кошельке + Получение наилучших курсов... + Плавающая ставка + Пользуясь сервисом, вы соглашаетесь с %s + Пользуясь сервисом, вы соглашаетесь с %1$s и %2$s + Больше провайдеров на подходе.\nСледите за обновлениями! + Политикой конфиденциальности + Провайдер + Лучший курс + Доступно до %s + Доступно с %s + Недоступно для этой пары + Требуется разрешение + Условиями использования + Токены не найдены. Пожалуйста, попробуйте другой запрос + ID транзакции скопирован + Информация ниже не является обязательной. Вы можете стереть её, если хотите. + Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. + Скажите, пожалуйста, какая у вас карта? + Привет, команда поддержки, + Пожалуйста, расскажите нам больше о вашей проблеме. Каждая маленькая деталь может помочь. + Мои предложения + Не могу отсканировать карту + Обращение в поддержку + Обращение в поддержку Tangem + Не могу отправить транзакцию + Купить + Сканировать + Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции + Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции + Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции + Приложите, чтобы отсканировать + Приложите, чтобы подписать + Приложите карту + Вы обновили данные биометрии, отсканируйте свою карту для входа + Чтобы начать отслеживать свои криптоактивы и транзакции, добавьте токены + Управление токенами + Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту + Отсканируйте карту + Обменивайте свои токены с %1$s комиссии провайдера через Changelly с %2$s по %3$s февраля. + Обмен с Changelly, %s комиссии + Токены + Забронировать + Оплатите его криптой и сэкономьте **50 долларов** через нашего партнера Travala: **%1s - %2s** + Забронируйте отпуск с Tangem + Добавить + Изменить + Рыночная капитализация + Основная сеть + Не основной или основной блокчейн, на котором размещен токен + Не основные сети + Выберите сети + Кошелек + Не удалось найти этот токен, вы можете добавить его вручную. + + %1$d из %2$d кошелька + %1$d из %2$d кошельков + %1$d из %2$d кошельков + %1$d из %2$d кошельков + + например Bitcoin + Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление. + Голосовать + Выберите кошелек + Кошелёк не поддерживает более одной сети + Вам необходимо установить единый код доступа для защиты всех ваших карт + Защита + Позже вы сможете установить индивидуальный код доступа для каждой карты + Персонализация + Код доступа можно восстановить с помощью привязанной карты. Не храните все карты в одном месте. + Восстановление + Выберите любое слово, фразу или число в качестве кода доступа + Создайте код доступа + Введите код доступа еще раз, чтобы избежать ошибки + Повторно введите код доступа + Код доступа должен состоять не менее чем из 4 символов. + Введенные коды доступа не совпадают + Необходимо повторить операцию, при этом карта будет сброшена к заводским настройкам + Ошибка активации + Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? + Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. + Парольная фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов. + Добавить резервную карту + Сканировать карту #%d + Создать резервную копию + Сканировать основную карту + Перейти к моему кошельку + Завершение бэкапа + Получить криптовалюту + Сканировать основную карту + Пропустить + Как это работает? + Давайте сгенерируем все ключи на вашей карте и создадим безопасный кошелек + Создать кошелек + Создать кошелек + Другие опции + Ваши ключи будут надежно сгенерированы внутри карты. Никакой seed-фразы, а это значит, что никто не может экспортировать или украсть ее. + Cоздавайте ключи приватно + Ваша карта активирована и готова к использованию + Успешно! + В этом случае вам будет необходимо начать процесс заново. + Вы хотите выйти из процесса активации? + Подготовка + Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Если на нем есть средства, пожалуйста сначала выведите их, а затем сделайте сброс до заводских настроек и используйте как резервную. + Резервная копия + Прочитать о seed-фразе + + + Запишите эти %d слова в порядке, указанном ниже, и сохраните их в надежном месте. + Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. + Запишите эти %d слов в порядке, указанном ниже, и сохраните их в надежном месте. + + Ваша seed-фраза + + + %d слова + %d слов + %d слов + + Чтобы импортировать кошелек, введите seed-фразу в поле ниже + Создать seed-фразу + Импорт кошелька + Seed-фраза — это набор слов, который дает возможность восстановить кошелек. В отличие от ключей, сгенерированных картой, seed-фраза не защищена и может быть скопирована и украдена. Используйте этот вариант на свой страх и риск. + Использовать seed-фразу + Неверная seed-фраза. Пожалуйста, проверьте порядок слов. + Неверная seed-фраза. Пожалуйста, проверьте орфографию. + Устаревший + Чтобы проверить, правильно ли вы записали seed-фразу, введите 2-е, 7-е и 11-е слова + Итак, проверим + Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. + Вы можете добавить еще одну карту или завершить процесс резервного копирования + Подготовьте резервную карту с номером %s + Отсканируйте основную карту, чтобы начать процесс резервного копирования. + Подготовьте основную карту с номером %s + Ваша карта настроена и готова к использованию. + Добавлено максимальное количество карт. Завершите процесс резервного копирования. + Активация карты + Резервная карта #%d + Нет резервных карт + Добавлена ​​одна резервная карта + Подготовьте свою карту + Добавлены две резервные карты + Пополните кошелек на любую сумму, чтобы начать пользоваться картой + Пополните кошелек более чем на %1$s %2$s, чтобы начать пользоваться картой + Купить криптовалюту + Показать адрес кошелька + Активация кошелька + Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. + Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала + Вы можете сделать резервную копию своих ключей на одной или двух других пустых картах Wallet. + Код доступа можно восстановить с помощью одной из резервных карт. + Все резервные карты являются полнофункциональными и содержат одинаковые ключи. + Вы сможете установить код доступа для защиты своих кошельков. + Резервная копия карты + Восстановление кода доступа + Идентичные карты + Код доступа + Группы + По балансу + Сортировка токенов + Список + Выбрать из галереи + Нравится наше приложение? + %1$s (%2$s) в сети %3$s + Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств. + Участвовать + Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. + Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. + Грядущие выплаты + Ваши друзья купили + Меньше + Больше + Нет грядущих выплат + + за %d кошелек + за %d кошелька + за %d кошельков + за %d кошельков + + Получите ^^%1$s^^ на ваш адрес в сети %2$s %3$s ^^спустя 30 дней^^ за каждый кошелек, который купит ваш друг + Вы + Получит + при покупке кошелька на сайте tangem.com + %s скидку + Ваш друг + Персональный код скопирован! + Ваш персональный код + Купи Tangem Wallet со скидкой!\n%s + Приведи друга в Tangem + Вы приняли + Нажимая на эту кнопку, вы принимаете + в реферальной программе + + %d кошелек + %d кошелька + %d кошельков + %d кошельков + + Сбросить карту + Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку + Я понимаю, что не смогу этой картой восстановить пароль на остальных картах этого кошелька, если я его забуду + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать данную карту для восстановления кода доступа. + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек. + У вас есть карта банка другой страны, а также вид на жительство или регистрация вне РФ? + Карты банков РФ в данный момент не принимаются + Войдите в приложение и следите за своим балансом без сканирования карты + Доступ в приложение + Использовать биометрию + Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты + Код доступа + Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков + Включите биометрическую аутентификацию + Вы хотите использовать биометрию? + Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта + Сканировать + Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. + Приготовьте свою карту + Уже содержится в введенном адресе + Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. + Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? + Причина: %1$s\nКод: %2$s + Транзакция не выполнена + Сумма + Вы можете установить комиссию за транзакцию, изменив значение в поле Satoshi per vByte. + %1$s, %2$s + Адрес + Код назначения + Введите адрес + Адрес совпадает с адресом кошелька + Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение. + Недопустимый Tag. Он не будет добавлен в транзакцию. + Недопустимый Memo. Он не будет добавлен в транзакцию. + Tag + Memo + Включая комиссию + Низкая + Нормальная + Приоритетная + Проверьте своё интернет соединение + Информация о комиссии сети недоступна + Из + Лимит газа + Это максимальное количество газа, которое будет потрачено на выполнение транзакции или контракта. Лимит газа предотвращает неожиданные или неограниченные расходы при выполнении транзакции. + Цена газа + Это стоимость, которую вы готовы заплатить за каждую единицу газа. Чем выше цена газа, тем быстрее ваша транзакция будет обработана. + Всё + Максимальная сумма + Комиссия не превысит + Допустим ввод только цифр + Покрытие сетевой комиссии + Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса + Недостаточно средств + Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе. + Экзистенциальный депозит + Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. + Установлена высокая комиссия + Ввиду особенности сети %1$s комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %2$s. + Комиссия повышена + Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению + Недопустимая сумма + Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %2$s. + Адрес получателя не активирован. \nПожалуйста, измените сумму отправки, чтобы продолжить. + Сумма отправки не может быть менее %s + Оставить %s + Уменьшить на %s + Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции + Возможны задержки по транзакции + Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. + Лимит транзакции + Опционально + Пожалуйста, совместите свой QR-код с квадратом, чтобы отсканировать его. Убедитесь, что вы сканируете адрес в сети %s. + Последние + Получатель + Неверный адрес + Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов. + Отправить + Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. + Мои кошельки + Способ измерения комиссии за биткоин-транзакцию. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый виртуальный байт в транзакции. Чем выше число, тем быстрее будет обработана транзакция майнерами. + Сатоши / вбайт + Отправка + Нажмите на любое поле, чтобы изменить его + Отправка %s + Вы отправляете **%1$s**, включая комиссию сети %2$s + Отправка %s + Всего + %1$s и %2$s будет отправлено + ≈ %1$s (вкл. комиссию: %2$s) + %s будет отправлено + Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время + Неверный адрес + Транзакция отправлена + Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. + Революционный аппаратный кошелек + До трех карт с одним кошельком + Все ключи в безопасности + Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте + Тысячи криптовалют + Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. + Кошелек для каждого + Встречайте Tangem + Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах + Поддержка Web 3.0 + В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя + В сумму включена комиссия провайдера сервиса. + Комиссии + Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен. + Подтвердить + Вы отправляете + Дать разрешение + Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму. + Недостаточно средств + Подтвердить + Текущая транзакция + Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена. + Дать разрешение + Укажите лимит доступа к выбранному токену + Количество %s + Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s + Безлимитно + В процессе + Обменять + Вы получите + Выберите токен + не доступен + Балансы скрыты + Балансы показаны + Отменить + Выбранная операция в данный момент недоступна. Попробуйте позже. + В данный момент покупка монеты %s недоступна. Следите за нашими обновлениями. + У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. + У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. + В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. + Продажа средств станет доступной после завершения транзакции(-ий) в сети %s + Отправка средств станет доступной после завершения транзакции(-ий) в сети %s + В данный момент продажа %s недоступна. Следите за нашими обновлениями. + Сгенерировать XPUB + Скрыть + Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. + Скрыть %s + Скрыть токен + %1$s токен в сети %%image%% %2$s + Токен в сети %%image%% %1$s + Токен %1$s (%2$s) является основной валютой в сети %3$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети + Невозможно скрыть %s + Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля. + Обмен с Changelly, %s комиссии + Обменять + контракт: %s + У вас еще нет транзакций + Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию. + Несколько адресов + История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе. + Операция + от: %s + на: %s + Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d + Вы отсканировали не ту twin-карту. Пожалуйста, попробуйте отсканировать другую + Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька. + Один кошелек. Две карты. + Сканировать карту #%s + Создание кошелька + Отсканируйте twin-карту #%s + Подготовка карты + Tangem Twin + Это действие необратимо. У вас не будет доступа к старому кошельку. + Приложите twin-карту с номером %s и не убирайте до окончания операции + Используйте %s или отсканируйте карту, чтобы получить доступ к своему кошельку + Добавить новый кошелек + Вы уверены, что хотите удалить этот кошелек? + Произошла ошибка, пожалуйста, отсканируйте свою карту для входа + Этот кошелек уже был сохранен, вы можете добавить другой + Кошелек с именем %s уже существует + Имя кошелька + Переименование кошелька + Разблокировать все + Разблокировать все с %s + Блокчейн недоступен. Попробуйте позже. + Отсканируйте карту + Запрос на подпись сообщения.\n\n%s + Dapp %1$s, запрос на\nподпись транзакции с BNB.\n\n%2$s + Торговый ордер на %1$s\nЦена: %2$s\nСумма к получению: %3$s\nСумма к оплате: %4$s + Детали транзакции:\nОт: %1$s\nК: %2$s\nСумма: %3$s + Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код + Запрос на создание транзакции для %1$s\n%2$s\n\nСумма: %3$s\nКомиссия: %4$s\nВсего: %5$s\nБаланс: %6$s + Невозможно отправить транзакцию. Недостаточно средств. + Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. + Не все токены добавлены в ваш список. Пожалуйста, добавьте их в начале, а потом попробуйте снова. Недостающие токены: \n + Не удалось подписать сообщение.\nПожалуйста, попробуйте еще раз + Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже. + Запрос на подключение через WalletConnect содержит неподдерживаемые блокчеины. Неподдерживаемые блокчеины:\n + Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. + Произошла непредвиденная ошибка. Сообщение ошибки: %s Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. + Неверная карта выбрана в приложении Tangem + Не удалось создать транзакцию из данных Dapp. Код: %s + Произошла непредвиденная ошибка. Код ошибки: %d Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. + Нет открытых сессий WalletConnect + Упс. Нет сессий. + Не удалось создать пару WalletConnect: %1$s + Вставить из буфера обмена + Сообщение для %1$s:\n%2$s + Запрос на открытие сессии для\n%1$s\n\nСЕТЬ: %2$s\n\nURL: %3$s + Операция не может быть завершена.\n\nВы уже установили сеанс WalletConnect с этими параметрами. + Сканировать новый код + Эту карту нельзя использовать с WalletConnect. + Сеть не поддерживается. Пожалуйста, выберите другую сеть. + Выберите сеть + Сессии WalletConnect + Подключение к dApps + WalletConnect + Рыночная цена %s + за 24 часа + Сеть %s + Адрес скопирован в буфер обмена + Нет соединения с интернетом + Tangem + Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку + Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян. + Ошибка активации + По решению разработчиков сети BNB, стандарт BEP-2 перестанет поддерживаться в июне 2024 года. Чтобы не потерять свои активы, их необходимо преобразовать в стандарт BEP-20. Используйте функцию обмена в приложении чтобы перевести их в cеть BNB Smart Chain. + Отключение сети BNB Beacon Chain + Можно лучше + Нравится + Понятно! + Очень круто! + Обновить + Вы находитесь в режиме демо + Демо режим включен + Отсканированная вами карта является картой разработчика. Не используйте ее для создания своего кошелька. + Не для пользователя! + Cеть %1$s использует концепцию экзистенциального депозита. Если баланс вашего счета будет ниже %2$s, то он будет деактивирован, а средства на счете уничтожены. + Для работы с сетью необходим депозит + Обмен будет доступен после завершения %s транзакции + У вас есть активная транзакция + Разрешение обмена в процессе и будет скоро завершено + Разрешение в процессе + Минимальная сумма обмена - %1$s. Пожалуйста, убедитесь, что остаток после обмена также не будет меньше %2$s. + У вас в списке нет монет доступных для обмена с %s + Нет доступных для обмена токенов + Чтобы совершить транзакцию, вам необходимо внести немного %1$s %2$s + Невозможно покрыть комиссию %s + Сумма получения не может быть менее %s + Cервис временно недоступен + Сумма для обмена должна быть не более %s + Сумма для обмена должна быть не менее %s + Пожалуйста, измените сумму для обмена + Возможно, данная карта - образец или подделка + Ошибка проверки подлинности + Ассоциировать + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%1$s %2$s + Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять + Ассоциируете свой токен + Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена + На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. + Малое количество подписей + Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. + + Используйте вашу карту, чтобы получить адрес для %d сети + Используйте вашу карту, чтобы получить адреса для %d сетей + Используйте вашу карту, чтобы получить адреса для %d сетей + Используйте вашу карту, чтобы получить адреса для %d сетей + + Некоторые адреса отсутствуют + В данный момент сеть недоступна. Пожалуйста, попробуйте позже. + Сеть недоступна + Пополните ваш кошелек + Ваш кошелек не имеет резервной копии. Проведите эту процедуру сейчас, чтобы защитить ваши активы. + Резервная копия отсутствует + Эта карта ранее использовалась для подписи транзакций. Если она получена от ненадежного источника, рассмотрите возможность вывода своих средств. Если это ваша карта, дополнительных действий не требуется. + Карта уже подписывала транзакции + Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше + Нравится Tangem? + Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его + Необходима плата за аренду сети + %1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети. + Недостаточно %1$s для оплаты комиссии сети + Сеть Солана испытывает высокую нагрузку. Если Ваша транзакция не прошла в течение 2 минут, повторите её отправку. + Оповещение сети Солана + Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату. + Некоторые сети в настоящее время недоступны. Пожалуйста, повторите попытку позже. + Некоторые сети недоступны + Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки. + Только для целей тестирования + Отказаться + Вы не закончили резервное копирование. Хотите продолжить? + Да, возобновить + Отказаться + Если сейчас отказаться, то придётся сбрасывать карты до заводских настроек, чтобы начать заново + Возобновить резервное копирование + Это необратимое действие + Войти с %s + Сканировать карту + Используйте %s или отсканируйте карту для входа в приложение + C возвращением! diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index ea0198dfa6..4f7f952256 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1,720 +1,713 @@ - Add custom token - Manage tokens - Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. - Request support - Try again - This feature is disabled in Demo mode - Reason: %s - Can\'t send a transaction - The selected does not support the %1$s network - To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. - Tokens in %1$s network are not supported by this card due to firmware limitation. - Thank you for your feedback. We will respond as soon as possible - Your suggestions were sent - Please try to tap the card exactly as shown in the animation or request support. - Are you having difficulty scanning your card? - This card is not designed to work with this app - Default Fee - Enable Default Fee to set transaction fees automatically and skip the Fee page when sending funds. You can always go back to this page if necessary. - Go to settings to enable biometric authentication in the Tangem App - Enable biometric authentication - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - Removing the saved card deletes all the saved wallets and their access codes from the app. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Dark - Light - System default - If system is selected, the app will auto-adjust based on your device\'s system settings - System - Theme - App Settings - To hide or show your balances, simply flip your device screen down, or switch it off in Settings - Don\'t show again - Got it - Balances are hidden - Please scan the card - Please try again in 30 seconds or scan the card - 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 - - %d card - %d cards - - Disable this option if you don\'t want this card to be used to reset access codes on other cards in this wallet. Please note that this will also prevent you from resetting the access code on this card. - Allows you to use this card to reset access code on other cards in this wallet - Access code recovery - Reset - Are you sure you want to do this? - Change Access Code - Access code will be changed on this card only - Reset to Factory Settings - Security Mode - Card Settings - In addition to network fee, the Cardano network charges %1$s ADA when transacting with the %2$s token - Cardano transaction requirements - To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value (5 ADA recommended) - Insufficient ADA for token transfer - You must maintain some ADA because you have some tokens on the Cardano blockchain - Not enough ADA - Accept - Access denied - Apply - Approval - Attention - Balance: %s - Balance - biometric authentication - biometrics - Buy - Go to %1$s - You have not given access to your camera, please adjust your privacy settings - Cancel - Close - Continue - Copy - Copy address - Create - Delete - Disabled - Disconnect - Done - Enable - Enabled - Error - Explore - Explore transaction history - Explorer - Fee - Network fees are charges users pay to process and confirm transactions. The fee amount can be affected by network congestion, transaction size, and execution priority. %s - Custom - Fast - Market - Slow - Speed and fee - Get addresses - Go to provider - Import - Locked - Main network - Network fee - Next - No - No address - OK - Primary Card - Passphrase - Paste - Read more - Receive - Reject - Reload - Rename - Retry - Save changes - Search - Search tokens - Seed phrase - Select action - Sell - Send - The server is not available, please try again later - Share - Sign - Sign and send - Start - Submit - Success - Support - Swap - terms and conditions - Transaction failed - Transactions - Transfer - I understand - There was an error. Please try again. - Unreachable - Warning - Yes - Contract address copied! - Available networks - Add token - Contract address - Contract address is invalid - Please select the network - Decimal must be a valid integer, up to %li - Custom derivation - E. g. m/00\'/0000\'/0\'/0/0 - Enter custom derivation - Decimals - Derivation Path - Default - BIP44 coin type - The derivation path you\'ve entered is not valid - E.g. USD Coin - Name - Not selected - Network - Token network - You can manually add a token that is not natively supported by Tangem - E.g. USDC - Symbol - Token symbol - This token/network has already been added to your list - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - Be aware of adding scam tokens, they can cost nothing - Note that tokens can be created by anyone - Chat - Access code - You will have to submit the correct access code before scanning the card - Long Tap - This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. - Passcode - Before executing any command entailing a change of the card state, you will have to enter the passcode. - Referral program - Flip your device screen down to quickly hide and show balances - %s hashes - Card ID - Contact to Support - Link More Cards - App Currency - Flip-to-Hide Balances - Issuer - Signed - If you forget the code you will lose access to your funds. Code recovery is not possible. - Details - Check your internet connection or switch to a different network - Terms of Service - You have used a card from another wallet. Tap the card associated with this wallet - My tokens - You haven\'t added any tokens yet. Add tokens via Market to swap - Cannot be swapped for %s - Provided by - Status - Tangem offers token swaps via 3rd-party providers according to each provider\'s terms - Choose provider - An error occurred. Code: %s - Oops! Swapping the selected pair through the chosen provider is temporarily unavailable. Please try again later. (Code: %s) - Selected provider is unavailable at the moment. Please try again later. (Code: %s) - Swaps are unavailable at the moment. Please try again later. (Code: %s) - Estimated amount - Exchange by %s - Visit provider’s website to refund your money - Operation failed by provider - Visit provider’s website for verification - KYC verification required by provider - Canceled - Confirmed - Confirming - Confirming... - Exchanged - Exchanging - Exchanging... - Failed - Deposit received - Awaiting deposit - Awaiting deposit... - Refunded - Sending to you - Sending to you... - Sent - Provider-sourced data. Estimated amount subject to change due to market conditions. - Exchange status - Verification required - List of all tokens added to your wallet - Fetching best rates... - Floating rate - By using swap functionality, you agree with provider’s %s - By using swap functionality, you agree with provider’s %1$s and %2$s - More providers are coming soon.\nStay tuned! - Privacy Policy - Provider - Best rate - Available up to %s - Available from %s - Unavailable for this pair - Permission Required - Terms of Use - No tokens found. Please try another request - ID: %s - Transaction ID copied - 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 - Hi support team, - Please tell us more about your issue. Every small detail can help. - My suggestions - Can\'t scan a card - Feedback - Tangem feedback - Can\'t send a transaction - Order card - Scan card - To change the access code tap the card as shown above and do not remove until the end of the operation - To change the passcode tap the card as shown above and do not remove until the end of the operation - To create the wallet tap the card as shown above and do not remove until the end of the operation - Tap to scan - Tap to sign - Tap the card - You have updated biometrics, scan your card to enter - To begin tracking your crypto assets and transactions, add tokens - Manage tokens - To access all the networks you need to scan the card - Scan your card - Enjoy %1$s service fees on swaps via Changelly from February %2$s-%3$s - Swap with Changelly, %s fees - Tokens - Add - Edit - Coin market cap - Blockchain the cryptocurrency was initially created - Native network - Using non-native networks for tokens enables cross-blockchain interoperability, allowing assets to be utilized in diverse decentralized applications and smart contracts across platforms. However, this often involves a custodian or smart contract to hold the original asset securely, introducing centralization and counterparty risk. - Not original or primary blockchain the token is hosted - Non-native networks - Choose networks - Wallet - Couldn’t find this token, you can add it manually - - %1$d of %2$d wallet - %1$d of %2$d wallets - - e.g. BTC I trust, hodl I must - The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it. - Upvote - Choose wallet - The wallet doesn\'t support more than one network - You have to set up a single access code to protect all your cards - Protect - You can set up an individual access code on each card later - Personalize - The access code can be restored with a linked card. Don’t keep all cards at one place. - Restore - Choose any word, phrase, or number you want as your access code - Create Access Code - Enter your access code one more time to avoid a mistake - Re-enter your Access Code - Access code must be at least 4 characters long - Entered access code didn\'t match the initial access code - Please repeat the operation. The card will be reset to factory settings. - Activation error - You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? - The backup process is partly complete. You can\'t exit it now. - The passphrase is an advanced security feature that crypto wallets use. It adds an extra word or phrase of your own choosing to your already existing recovery phrase to unlock a brand-new set of addresses. - Add a backup card - Scan the card #%d - Backup now - Scan the primary card - Continue to my wallet - Finalize the backup - Receive crypto - Scan primary card - Skip for later - How does it work? - Let\'s generate all the keys on your card and create a secure wallet - Create wallet - Create a wallet - Other options - Your keys will be securely generated inside the card. There is no seed phrase, which means nobody can export or steal it. - Generate keys privately - Your card is activated and ready to be used - Success! - In this case, you will need to start from the beginning. - Do you want to exit the activation process? - Getting started - Another wallet has already been created on the card you\'re trying to add. If you have funds in this wallet, please withdraw it and then reset this card and add it as a backup. - Creating a backup - Read more about seed phrase - - - Write these %d words down in the order given below and store them in a safe and secret place. - - Your seed phrase - - - %d words - - To import your wallet, enter your seed phrase in the field below - Generate seed phrase - Import wallet - A seed phrase is a series of words that allows you to recover your wallet. Unlike the keys generated by the card, seed phrases are unprotected and can be copied and stolen. Use this option at your own risk. - Use seed phrase - Invalid seed phrase. Please check the word order. - Invalid seed phrase. Please check your spelling. - Legacy - To check whether you’ve written down your seed phrase correctly, please enter the 2nd, 7th and 11th words - So, let’s check - To start the backup process add up to two backup cards. - You can add one more card or finalize the backup process - Prepare the backup card with number %s - Scan the primary card to start the backup process. - Prepare the primary card with number %s - Your wallet card is configured and ready for use. - Max number of cards added. Finalize the backup process. - Activating card - Backup card #%d - No backup cards - One backup card added - Prepare your card - Two backup cards added - To get started, simply top up the wallet with any amount - To get started, simply top up the wallet with more than %1$s %2$s - Buy crypto - Show the wallet\'s address - Activate a wallet - The twinning process is partly complete. You can\'t exit it now. - If the process of creating the wallet gets interrupted in any way, you\'ll have to start over - You can backup your keys up to two other blank Tangem Wallet cards. - Access code can be restored with one of backup cards. - All the backup cards can be used as full-functional with the identical keys. - You will be able to set an access code to protect your wallets. - Backup wallet - Access code restore - Identical cards - Access code - Group - By balance - Organize tokens - Ungroup - Select from the gallery - Settings - You have not given access to your camera - Camera access denied - Enjoying our app? - %1$s (%2$s) on %3$s network - Send only %s to this address. Sending any other currency will result in its irreversible loss. - 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. - Upcoming payments - Your friends bought - Less - More - No upcoming payments - - for %d wallet - for %d wallets - - Will get ^^%1$s^^ for each wallet bought by your friend on your %2$s network address %3$s ^^30 days after^^ that - You - Will get a - when buying a wallet on tangem.com - %s discount - Your friend - Personal code copied! - Your personal code - Buy Tangem Wallet with discount!\n%s - Refer your friends to Tangem - You\'ve accepted - By tapping this button you accept - of the referral program - - %d wallet - %d wallets - - Reset the Card - I understand that after performing this action, I will no longer have access to the current wallet - I realize that I can\'t use this card to recover my access code on the other cards of the current wallet - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. - Do you have a bank card from another country and a residence permit or registration outside the Russian Federation? - Russian bank cards are not currently accepted - Log into the app and check your balance without scanning the card - Access the app - Allow to use biometrics - Biometrics will be requested instead of the access code for interactions with your wallet - Access code - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable biometric authorization - Would you like to use biometrics? - Note that making a transaction with your funds will still require your card - Scan Card - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Get your card ready! - Already included in the entered address - The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. - You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? - Reason: %1$s\nCode: %2$s - The transaction is not completed - Amount - Base fee - Represents the part of the transaction fee that goes to the miner - %1$s, %2$s - Address - Destination Tag - Enter address - Address is the same as wallet address - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Include fee - Low - Normal - Priority - Check your network connection - Network fee info unreachable - From - Gas limit - This is the maximum amount of gas that will be spent to complete a transaction or contract. A gas limit prevents unexpected or unlimited charges when executing a transaction. - Gas price - This is the cost you are willing to pay for each unit of gas. The higher the gas price, the faster your transaction will be processed. - Max - Maximum amount - Max fee - The fee that will be charged for your transaction. You can set your own value. - Numbers only for Destination Tag - Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level - Network fee coverage - Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance - Total exceeds balance - Leave %s - The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance. - Existential deposit - The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. - Custom fee is high - Due to the peculiarities of the Tezos network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %s. - The fee is higher - The included commission exceeds the transfer amount, leading to a negative value - Invalid amount - The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s. - Target account is not created. Please change the amount to send. - The amount to send must be at least %s - Reduce by %s - Reduce to %s - Kindly be aware that your transaction may experience delays under specific fee settings - Transaction delays are possible - Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount. - Transaction limitation - Optional - Priority fee - Represents the minimum gasUsed multiplier required for a transaction to be included in a block. This is the part of the transaction fee that is burnt. - Please align your QR code with the square to scan it. Ensure you scan %s network address. - Recent - Recipient - Not a valid address - Ensure that you are sending funds to an %s wallet address. Errors may result in the loss of your tokens - Send to - A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds - My wallets - A way of measuring Bitcoin transaction fees. It indicates the number of the smallest Bitcoin unit (Satoshi) for each virtual byte in a transaction. The higher the number, the faster the transaction will be processed by miners. - Satoshi / vByte - Sending... - Tap any field to change it - Send %s - You are sending %1$s including a network fee of %2$s - Sending %s - Total - %1$s and %2$s will be sent - ≈ %1$s (inc. fee: %2$s) - %s will be sent - Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while - Invalid address - %1$s (%2$s) - Transaction sent - Store your crypto assets secure while keeping private keys contained in your card - Revolutionary Hardware Wallet - Up to 3 physical cards to one wallet - Ultra Secure Backup - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - Thousands of Currencies - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - The Wallet for Everyone - Meet Tangem - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - Web 3.0 Compatible - The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address. - The amount includes the service provider\'s fee. - Fees - All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval. - Approve - You swap - Give Permission - Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome. - Insufficient funds - Sending amount will be reduced to cover the selected fee level - Approve - Current transaction - The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. - Give Permission - Specify the approve limit for the selected token - Amount %s - To continue, grant 1inch smart contracts permission to use your %s - Unlimited - In progress - Swap - You receive - Choose token - not available - Balances hidden - Balances shown - Undo - This operation is currently unavailable. Please try again later. - Buying %s is not available at the moment. Please check our updates. - You do not have funds to sell. Top up your account to be able to sell funds from it. - You do not have funds to send. Top up your account to be able to send funds from it. - Swapping %s is not available at the moment. Please check our updates. - Selling funds will be available once the pending transaction(s) in network %s is complete - Sending funds will be available once the pending transaction(s) in network %s is complete - Selling %s is not available at the moment. Please check our updates. - Choose address - Generate XPUB - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Hide %s - Hide token - %1$s token in %%image%% %2$s network - Token in %%image%% %1$s network - The %1$s token is the main currency on the %2$s network and cannot be hidden as long as you have other tokens on this network in the list. - Unable to hide %s - Exchange this token for another at %1$s service fees from February %2$s-%3$s. - Swap with Changelly, %s fees - Swap now - contract: %s - You don\'t have any transactions yet - Failed to load transaction history.\nClick on reload button to update the information. - Multiple addresses - Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer. - Operation - from: %s - to: %s - You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d - You\'ve scanned wrong twin card. Please try another one - This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. - One wallet. Two cards. - Scan the card #%s - Creating wallet - Scan the #%s twin card - Preparing card - Tangem Twin - This action is irreversible. You will not have access to the old wallet. - Tap the twin card with number %s and do not remove until the end of the operation - Use %s or scan a card to have an access to your wallet - Add new wallet - Are you sure you want to delete this wallet? - An error has occurred, please scan your card to log in - This wallet has already been saved, you can add another one - Wallet name - Rename Wallet - Unlock all - Unlock all with %s - Blockchain is unreachable. Try later - Scan the card - Requesting to sign a message.\n\n%s - Dapp %1$s, requesting to\nsign BNB transaction.\n\n%2$s - Trade order for %1$s\nPrice: %2$s\nAmount to receive: %3$s\nAmount to pay: %4$s - Transaction details:\nFrom: %1$s\nTo: %2$s\nAmount: %3$s - Clipboard contain WalletConnect code. Use copied value or scan QR-code - Request to create transaction for %1$s\n%2$s\n\nAmount: %3$s\nFee: %4$s\nTotal: %5$s\nBalance: %6$s - Can\'t send transaction. Not enough funds. - Failed to establish WalletConnect session. Please, try again later. - Not all tokens were added to your list. Please add them first and try again. Missing tokens:\n - Failed to sign message.\nPlease, try again - Failed to establish WalletConnect session: timeout error. Please, try again later. - Session request contains unsupported blockchains for WalletConnect connection. Unsupported blockchains:\n - Connection with this Dapp cannot be established due to its technical implementation. - We\'ve encountered unknown error. Error message: %s. If the problem persists — feel free to contact our support - Wrong card selected in Tangem App - Failed to create transaction from Dapp data. Code: %s - We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support - No opened WalletConnect sessions - Ooops. No Sessions. - Failed to pairing WalletConnect session: %1$s - Paste from clipboard - Message for %1$s:\n%2$s - Request to start a session for\n%1$s\n\nNETWORK: %2$s\n\nURL: %3$s - The operation couldn\'t be completed.\n\nYou have already established a WalletConnect session with this parameters. - Scan new code - This card can\'t be used to establish WalletConnect session - This network is not supported. Please select another network. - Select network - WalletConnect Sessions - Connect to dApps - WalletConnect - %s Market Price - last 24h - %s network - Address was copied to clipboard - No internet connection - Tangem - Use %s or scan a card to unlock access to your wallet - Please withdraw all funds from this wallet, reset it to factory settings, and create a new one. Access to the current wallet will be lost. - Activation error - Could be better - Like it - Ok, Got it! - Really cool! - Refresh - You are currently in the Demo mode - Demo mode active - The card you scanned is a developer card. Do not use it to create your wallet. - Not for users! - %1$s network requires an Existential Deposit. If your account drops below %2$s, it will be deactivated, and any remaining funds will be destroyed. - Network requires Existential Deposit - Swap will be available after the %s transaction is complete - You have active transaction - Swap approval is underway and will be completed shortly - Approval in progress - The minimum swapping amount is %1$s. Please ensure that the remaining balance after the swap will not be less than %2$s. - You do not have any %s exchangeable coins in your list - No available tokens to swap - To make a transaction you need to deposit some %1$s %2$s - Unable to cover %s fee - The amount to receive must be at least %s - Service temporarily unavailable - The amount of tokens to be swapped must not exceed %s - The amount to swap must be at least %s - Please change the amount to swap - This card might be a production sample or counterfeit - Authenticity check failed - Associate - This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s - This token must be associated with your Hedera account before you can receive it - Associate your token - Hot enough %s. Top up your Hedera account to associate this token - Only %s signatures are left on this card. You must withdraw all of your funds. - Low signature count - Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. - - Use your card to get an address for %d network - Use your card to get an addresses for %d networks - - Some addresses are missing - The network is currently unreachable. Please try again later. - Network is unreachable - Top up your wallet - Your wallet hasn\'t been backed up. Carry out this procedure to protect your assets now. - Missing backup - This card has been previously used for transactions. If received from an untrusted source, consider withdrawing all funds. If it\'s your card, no action is required. - Card has already signed transactions - Your review keeps us motivated to make Tangem Wallet even better - Enjoying Tangem? - You must associate your token before receiving tokens - Network rent fee required - %1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. - Insufficient %1$s to cover network fee - Transaction pending - The Solana network is congested. If your transaction is not processed within 2 minutes, please repeat the transaction. - Solana Network Alert - Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free. - Some networks currently are unreachable. Please try again later. - Some networks are unreachable - This is a Testnet card. It cannot process transactions and should only be used for testing and development purposes. - For testing purposes only - Discard - You have an interrupted backup. Do you want to resume? - Yes, resume - Discard - If you will discard the backup now, then you will have to reset the cards to factory settings to start over again - Resume backup - This is an irreversible action - Log in with %s - Scan card - Use %s or scan a card to access the app - Welcome back! + Add custom token + Manage tokens + Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. + Request support + This feature is disabled in Demo mode + Reason: %s + Can\'t send a transaction + The selected does not support the %1$s network + To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset. + Tokens in %1$s network are not supported by this card due to firmware limitation. + Are you having difficulty scanning your card? + This card is not designed to work with this app + Default Fee + Enable Default Fee to set transaction fees automatically and skip the Fee page when sending funds. You can always go back to this page if necessary. + Go to settings to enable biometric authentication in the Tangem App + Enable biometric authentication + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + Removing the saved card deletes all the saved wallets and their access codes from the app. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Dark + Light + System default + Theme + App Settings + To hide or show your balances, simply flip your device screen down, or switch it off in Settings + Don\'t show again + Got it + Balances are hidden + Please scan the card + Please try again in 30 seconds or scan the card + 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 + + %d card + %d cards + + Disable this option if you don\'t want this card to be used to reset access codes on other cards in this wallet. Please note that this will also prevent you from resetting the access code on this card. + Allows you to use this card to reset access code on other cards in this wallet + Access code recovery + Reset + Are you sure you want to do this? + Change Access Code + Access code will be changed on this card only + Reset to Factory Settings + Security Mode + Card Settings + In addition to network fee, the Cardano network charges %1$s ADA when transacting with the %2$s token + Cardano transaction requirements + To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value (5 ADA recommended) + Insufficient ADA for token transfer + You must maintain some ADA because you have some tokens on the Cardano blockchain + Not enough ADA + Accept + Access denied + Apply + Approval + Attention + Balance: %s + Balance + biometric authentication + biometrics + Buy + Go to %1$s + You have not given access to your camera, please adjust your privacy settings + Cancel + Close + Continue + Copy + Copy address + Create + Delete + Disabled + Done + Enable + Enabled + Error + Explore + Explore transaction history + Explorer + Fee + Network fees are charges users pay to process and confirm transactions. The fee amount can be affected by network congestion, transaction size, and execution priority. %s + Custom + Fast + Market + Slow + Speed and fee + Get addresses + Go to provider + Import + Locked + Main network + Network fee + Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level + Next + No + No address + OK + Primary Card + Passphrase + Paste + Read more + Receive + Reject + Reload + Rename + Save changes + Search + Search tokens + Seed phrase + Select action + Sell + Send + The server is not available, please try again later + Share + Sign + Sign and send + Start + Submit + Success + Support + Swap + terms and conditions + Transaction failed + Transactions + Transfer + I understand + There was an error. Please try again. + Unreachable + Yes + Contract address copied! + Available networks + Add token + Contract address + Contract address is invalid + Please select the network + Decimal must be a valid integer, up to %li + Custom derivation + E. g. m/00\'/0000\'/0\'/0/0 + Enter custom derivation + Decimals + Derivation Path + Default + BIP44 coin type + The derivation path you\'ve entered is not valid + E.g. USD Coin + Name + Not selected + Network + Token network + You can manually add a token that is not natively supported by Tangem + E.g. USDC + Symbol + Token symbol + This token/network has already been added to your list + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + Be aware of adding scam tokens, they can cost nothing + Note that tokens can be created by anyone + Chat + Access code + You will have to submit the correct access code before scanning the card + Long Tap + This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. + Passcode + Before executing any command entailing a change of the card state, you will have to enter the passcode. + Referral program + Flip your device screen down to quickly hide and show balances + %s hashes + Card ID + Contact to Support + Link More Cards + App Currency + Flip-to-Hide Balances + Issuer + Signed + Details + Check your internet connection or switch to a different network + Terms of Service + You have used a card from another wallet. Tap the card associated with this wallet + My tokens + You haven\'t added any tokens yet. Add tokens via Market to swap + Cannot be swapped for %s + Provided by + Status + Tangem offers token swaps via 3rd-party providers according to each provider\'s terms + Choose provider + An error occurred. Code: %s + Oops! Swapping the selected pair through the chosen provider is temporarily unavailable. Please try again later. (Code: %s) + Selected provider is unavailable at the moment. Please try again later. (Code: %s) + Swaps are unavailable at the moment. Please try again later. (Code: %s) + Estimated amount + Exchange by %s + Visit provider’s website to refund your money + Operation failed by provider + Visit provider’s website for verification + KYC verification required by provider + Canceled + Confirmed + Confirming + Confirming... + Exchanged + Exchanging + Exchanging... + Failed + Deposit received + Awaiting deposit + Awaiting deposit... + Refunded + Sending to you + Sending to you... + Sent + Provider-sourced data. Estimated amount subject to change due to market conditions. + Exchange status + Verification required + List of all tokens added to your wallet + Fetching best rates... + Floating rate + By using swap functionality, you agree with provider’s %s + By using swap functionality, you agree with provider’s %1$s and %2$s + More providers are coming soon.\nStay tuned! + Privacy Policy + Provider + Best rate + Available up to %s + Available from %s + Unavailable for this pair + Permission Required + Terms of Use + No tokens found. Please try another request + ID: %s + Transaction ID copied + 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 + Hi support team, + Please tell us more about your issue. Every small detail can help. + My suggestions + Can\'t scan a card + Feedback + Tangem feedback + Can\'t send a transaction + Order card + Scan card + To change the access code tap the card as shown above and do not remove until the end of the operation + To change the passcode tap the card as shown above and do not remove until the end of the operation + To create the wallet tap the card as shown above and do not remove until the end of the operation + Tap to scan + Tap to sign + Tap the card + You have updated biometrics, scan your card to enter + To begin tracking your crypto assets and transactions, add tokens + Manage tokens + To access all the networks you need to scan the card + Scan your card + Enjoy %1$s service fees on swaps via Changelly from February %2$s-%3$s + Swap with Changelly, %s fees + Tokens + Book now + Save **$50** while booking via our partner Travala: **%1s - %2s** + Book your holidays with Tangem and pay in crypto + Add + Edit + Coin market cap + Blockchain the cryptocurrency was initially created + Native network + Using non-native networks for tokens enables cross-blockchain interoperability, allowing assets to be utilized in diverse decentralized applications and smart contracts across platforms. However, this often involves a custodian or smart contract to hold the original asset securely, introducing centralization and counterparty risk. + Not original or primary blockchain the token is hosted + Non-native networks + Choose networks + Wallet + Couldn’t find this token, you can add it manually + + %1$d of %2$d wallet + %1$d of %2$d wallets + + e.g. BTC I trust, hodl I must + The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it. + Upvote + Choose wallet + The wallet doesn\'t support more than one network + You have to set up a single access code to protect all your cards + Protect + You can set up an individual access code on each card later + Personalize + The access code can be restored with a linked card. Don’t keep all cards at one place. + Restore + Choose any word, phrase, or number you want as your access code + Create Access Code + Enter your access code one more time to avoid a mistake + Re-enter your Access Code + Access code must be at least 4 characters long + Entered access code didn\'t match the initial access code + Please repeat the operation. The card will be reset to factory settings. + Activation error + You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? + The backup process is partly complete. You can\'t exit it now. + The passphrase is an advanced security feature that crypto wallets use. It adds an extra word or phrase of your own choosing to your already existing recovery phrase to unlock a brand-new set of addresses. + Add a backup card + Scan the card #%d + Backup now + Scan the primary card + Continue to my wallet + Finalize the backup + Receive crypto + Scan primary card + Skip for later + How does it work? + Let\'s generate all the keys on your card and create a secure wallet + Create wallet + Create a wallet + Other options + Your keys will be securely generated inside the card. There is no seed phrase, which means nobody can export or steal it. + Generate keys privately + Your card is activated and ready to be used + Success! + In this case, you will need to start from the beginning. + Do you want to exit the activation process? + Getting started + Another wallet has already been created on the card you\'re trying to add. If you have funds in this wallet, please withdraw it and then reset this card and add it as a backup. + Creating a backup + Read more about seed phrase + + + Write these %d words down in the order given below and store them in a safe and secret place. + + Your seed phrase + + + %d words + + To import your wallet, enter your seed phrase in the field below + Generate seed phrase + Import wallet + A seed phrase is a series of words that allows you to recover your wallet. Unlike the keys generated by the card, seed phrases are unprotected and can be copied and stolen. Use this option at your own risk. + Use seed phrase + Invalid seed phrase. Please check the word order. + Invalid seed phrase. Please check your spelling. + Legacy + To check whether you’ve written down your seed phrase correctly, please enter the 2nd, 7th and 11th words + So, let’s check + To start the backup process add up to two backup cards. + You can add one more card or finalize the backup process + Prepare the backup card with number %s + Scan the primary card to start the backup process. + Prepare the primary card with number %s + Your wallet card is configured and ready for use. + Max number of cards added. Finalize the backup process. + Activating card + Backup card #%d + No backup cards + One backup card added + Prepare your card + Two backup cards added + To get started, simply top up the wallet with any amount + To get started, simply top up the wallet with more than %1$s %2$s + Buy crypto + Show the wallet\'s address + Activate a wallet + The twinning process is partly complete. You can\'t exit it now. + If the process of creating the wallet gets interrupted in any way, you\'ll have to start over + You can backup your keys up to two other blank Tangem Wallet cards. + Access code can be restored with one of backup cards. + All the backup cards can be used as full-functional with the identical keys. + You will be able to set an access code to protect your wallets. + Backup wallet + Access code restore + Identical cards + Access code + Group + By balance + Organize tokens + Ungroup + Select from the gallery + Settings + You have not given access to your camera + Camera access denied + Enjoying our app? + %1$s (%2$s) on %3$s network + Send only %s to this address. Sending any other currency will result in its irreversible loss. + 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. + Upcoming payments + Your friends bought + Less + More + No upcoming payments + + for %d wallet + for %d wallets + + Will get ^^%1$s^^ for each wallet bought by your friend on your %2$s network address %3$s ^^30 days after^^ that + You + Will get a + when buying a wallet on tangem.com + %s discount + Your friend + Personal code copied! + Your personal code + Buy Tangem Wallet with discount!\n%s + Refer your friends to Tangem + You\'ve accepted + By tapping this button you accept + of the referral program + + %d wallet + %d wallets + + Reset the Card + I understand that after performing this action, I will no longer have access to the current wallet + I realize that I can\'t use this card to recover my access code on the other cards of the current wallet + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Do you have a bank card from another country and a residence permit or registration outside the Russian Federation? + Russian bank cards are not currently accepted + Log into the app and check your balance without scanning the card + Access the app + Allow to use biometrics + Biometrics will be requested instead of the access code for interactions with your wallet + Access code + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable biometric authorization + Would you like to use biometrics? + Note that making a transaction with your funds will still require your card + Scan Card + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Get your card ready! + Already included in the entered address + The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. + You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? + Reason: %1$s\nCode: %2$s + The transaction is not completed + Amount + Base fee + Represents the part of the transaction fee that goes to the miner + You can set your transaction fee by adjusting the value in the Satoshi per vByte field. + %1$s, %2$s + Address + Destination Tag + Enter address + Address is the same as wallet address + The fee that will be charged for your transaction. You can set your own value. + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Include fee + Low + Normal + Priority + Check your network connection + Network fee info unreachable + From + Gas limit + This is the maximum amount of gas that will be spent to complete a transaction or contract. A gas limit prevents unexpected or unlimited charges when executing a transaction. + Gas price + This is the cost you are willing to pay for each unit of gas. The higher the gas price, the faster your transaction will be processed. + Max + Maximum amount + Max fee + Numbers only for Destination Tag + Network fee coverage + Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance + Total exceeds balance + The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance. + Existential deposit + The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. + Custom fee is high + Due to the peculiarities of the %1$s network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %2$s. + The fee is higher + The included commission exceeds the transfer amount, leading to a negative value + Invalid amount + The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s. + Target account is not created. Please change the amount to send. + The amount to send must be at least %s + Leave %s + Reduce by %s + Kindly be aware that your transaction may experience delays under specific fee settings + Transaction delays are possible + Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount. + Transaction limitation + Optional + Priority fee + Represents the minimum gasUsed multiplier required for a transaction to be included in a block. This is the part of the transaction fee that is burnt. + Please align your QR code with the square to scan it. Ensure you scan %s network address. + Recent + Recipient + Not a valid address + Ensure the receiving wallet address is on the %s network to avoid losing your tokens + Send to + A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds + My wallets + A way of measuring Bitcoin transaction fees. It indicates the number of the smallest Bitcoin unit (Satoshi) for each virtual byte in a transaction. The higher the number, the faster the transaction will be processed by miners. + Satoshi / vByte + Sending... + Tap any field to change it + Send %s + You are sending **%1$s** including a network fee of %2$s + Sending %s + Total + %1$s and %2$s will be sent + ≈ %1$s (inc. fee: %2$s) + %s will be sent + Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while + Invalid address + %1$s (%2$s) + Transaction sent + Store your crypto assets secure while keeping private keys contained in your card + Revolutionary Hardware Wallet + Up to 3 physical cards to one wallet + Ultra Secure Backup + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + Thousands of Currencies + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + The Wallet for Everyone + Meet Tangem + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + Web 3.0 Compatible + The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address. + The amount includes the service provider\'s fee. + Fees + All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval. + Approve + You swap + Give Permission + Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome. + Insufficient funds + Approve + Current transaction + The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. + Give Permission + Specify the approve limit for the selected token + Amount %s + To continue, grant 1inch smart contracts permission to use your %s + Unlimited + In progress + Swap + You receive + Choose token + not available + Balances hidden + Balances shown + Undo + This operation is currently unavailable. Please try again later. + Buying %s is not available at the moment. Please check our updates. + You do not have funds to sell. Top up your account to be able to sell funds from it. + You do not have funds to send. Top up your account to be able to send funds from it. + Swapping %s is not available at the moment. Please check our updates. + Selling funds will be available once the pending transaction(s) in network %s is complete + Sending funds will be available once the pending transaction(s) in network %s is complete + Selling %s is not available at the moment. Please check our updates. + Generate XPUB + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Hide %s + Hide token + %1$s token in %%image%% %2$s network + Token in %%image%% %1$s network + The %1$s (%2$s) token is the main currency on the %3$s network and cannot be hidden as long as you have other tokens on this network in the list + Unable to hide %s + Exchange this token for another at %1$s service fees from February %2$s-%3$s. + Swap with Changelly, %s fees + Swap now + contract: %s + You don\'t have any transactions yet + Failed to load transaction history.\nClick on reload button to update the information. + Multiple addresses + Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer. + Operation + from: %s + to: %s + You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d + You\'ve scanned wrong twin card. Please try another one + This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. + One wallet. Two cards. + Scan the card #%s + Creating wallet + Scan the #%s twin card + Preparing card + Tangem Twin + This action is irreversible. You will not have access to the old wallet. + Tap the twin card with number %s and do not remove until the end of the operation + Use %s or scan a card to have an access to your wallet + Add new wallet + Are you sure you want to delete this wallet? + An error has occurred, please scan your card to log in + This wallet has already been saved, you can add another one + The wallet with name %s already exists + Wallet name + Rename Wallet + Unlock all + Unlock all with %s + Blockchain is unreachable. Try later + Scan the card + Requesting to sign a message.\n\n%s + Dapp %1$s, requesting to\nsign BNB transaction.\n\n%2$s + Trade order for %1$s\nPrice: %2$s\nAmount to receive: %3$s\nAmount to pay: %4$s + Transaction details:\nFrom: %1$s\nTo: %2$s\nAmount: %3$s + Clipboard contain WalletConnect code. Use copied value or scan QR-code + Request to create transaction for %1$s\n%2$s\n\nAmount: %3$s\nFee: %4$s\nTotal: %5$s\nBalance: %6$s + Can\'t send transaction. Not enough funds. + Failed to establish WalletConnect session. Please, try again later. + Not all tokens were added to your list. Please add them first and try again. Missing tokens:\n + Failed to sign message.\nPlease, try again + Failed to establish WalletConnect session: timeout error. Please, try again later. + Session request contains unsupported blockchains for WalletConnect connection. Unsupported blockchains:\n + Connection with this Dapp cannot be established due to its technical implementation. + We\'ve encountered unknown error. Error message: %s. If the problem persists — feel free to contact our support + Wrong card selected in Tangem App + Failed to create transaction from Dapp data. Code: %s + We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support + No opened WalletConnect sessions + Ooops. No Sessions. + Failed to pairing WalletConnect session: %1$s + Paste from clipboard + Message for %1$s:\n%2$s + Request to start a session for\n%1$s\n\nNETWORK: %2$s\n\nURL: %3$s + The operation couldn\'t be completed.\n\nYou have already established a WalletConnect session with this parameters. + Scan new code + This card can\'t be used to establish WalletConnect session + This network is not supported. Please select another network. + Select network + WalletConnect Sessions + Connect to dApps + WalletConnect + %s Market Price + last 24h + %s network + Address was copied to clipboard + No internet connection + Tangem + Use %s or scan a card to unlock access to your wallet + Please withdraw all funds from this wallet, reset it to factory settings, and create a new one. Access to the current wallet will be lost. + Activation error + According to BNB network developers, support for the BEP-2 standard will end in June 2024. To avoid losing assets with this standard, please convert them to the BEP-20 standard. Use our swap service to transfer them to the BNB Smart Chain network. + BNB Beacon Chain will shut down + Could be better + Like it + Ok, Got it! + Really cool! + Refresh + You are currently in the Demo mode + Demo mode active + The card you scanned is a developer card. Do not use it to create your wallet. + Not for users! + %1$s network requires an Existential Deposit. If your account drops below %2$s, it will be deactivated, and any remaining funds will be destroyed. + Network requires Existential Deposit + Swap will be available after the %s transaction is complete + You have active transaction + Swap approval is underway and will be completed shortly + Approval in progress + The minimum swapping amount is %1$s. Please ensure that the remaining balance after the swap will not be less than %2$s. + You do not have any %s exchangeable coins in your list + No available tokens to swap + To make a transaction you need to deposit some %1$s %2$s + Unable to cover %s fee + The amount to receive must be at least %s + Service temporarily unavailable + The amount of tokens to be swapped must not exceed %s + The amount to swap must be at least %s + Please change the amount to swap + This card might be a production sample or counterfeit + Authenticity check failed + Associate + This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s + This token must be associated with your Hedera account before you can receive it + Associate your token + Not enough %s. Top up your Hedera account to associate this token + Only %s signatures are left on this card. You must withdraw all of your funds. + Low signature count + Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. + + Use your card to get an address for %d network + Use your card to get an addresses for %d networks + + Some addresses are missing + The network is currently unreachable. Please try again later. + Network is unreachable + Top up your wallet + Your wallet hasn\'t been backed up. Carry out this procedure to protect your assets now. + Missing backup + This card has been previously used for transactions. If received from an untrusted source, consider withdrawing all funds. If it\'s your card, no action is required. + Card has already signed transactions + Your review keeps us motivated to make Tangem Wallet even better + Enjoying Tangem? + You must associate your token before receiving tokens + Network rent fee required + %1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee. + Insufficient %1$s to cover network fee + The Solana network is congested. If your transaction is not processed within 2 minutes, please repeat the transaction. + Solana Network Alert + Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free. + Some networks currently are unreachable. Please try again later. + Some networks are unreachable + This is a Testnet card. It cannot process transactions and should only be used for testing and development purposes. + For testing purposes only + Discard + You have an interrupted backup. Do you want to resume? + Yes, resume + Discard + If you will discard the backup now, then you will have to reset the cards to factory settings to start over again + Resume backup + This is an irreversible action + Log in with %s + Scan card + Use %s or scan a card to access the app + Welcome back! diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 961dc949c5..e7c5bfc4e7 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -46,4 +46,5 @@ dependencies { implementation(deps.zxing.qrCore) implementation(deps.jodatime) implementation(deps.timber) + implementation(deps.markdown) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt index 73a58d56a1..d39c09ecce 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt @@ -95,10 +95,14 @@ fun AmountTextField( SimpleTextField( value = value, onValueChange = { newText -> - if (decimalFormat.isValidSymbols(newText)) { - val trimmed = decimalFormat.getValidatedNumberWithFixedDecimals(newText, decimals) - onValueChange(trimmed) - } + onValueChange( + prepareEnter( + oldValue = value, + newValue = newText, + decimalFormat = decimalFormat, + decimals = decimals, + ), + ) }, textStyle = textStyle.copy( fontSize = fontSize, @@ -117,8 +121,37 @@ fun AmountTextField( } } +private fun prepareEnter(oldValue: String, newValue: String, decimalFormat: DecimalFormat, decimals: Int): String { + val decimalSymbol = decimalFormat.decimalFormatSymbols.decimalSeparator + return if (decimalFormat.isValidSymbols(newValue)) { + val parsedValue = newValue.parseBigDecimalOrNull()?.toPlainString() + ?: if (newValue.isBlank()) "" else oldValue + val replacedWithSymbol = if (parsedValue.findLast { it != decimalSymbol } != null) { + when { + parsedValue.findLast { it == COMMA_SEPARATOR } != null -> { + parsedValue.replace(COMMA_SEPARATOR, decimalSymbol) + } + parsedValue.findLast { it == POINT_SEPARATOR } != null -> { + parsedValue.replace(POINT_SEPARATOR, decimalSymbol) + } + else -> parsedValue + } + } else { + parsedValue + } + val joinedSymbol = if (newValue.endsWith(COMMA_SEPARATOR) || newValue.endsWith(POINT_SEPARATOR)) { + replacedWithSymbol.plus(decimalSymbol) + } else { + replacedWithSymbol + } + decimalFormat.getValidatedNumberWithFixedDecimals(joinedSymbol, decimals) + } else { + oldValue + } +} + private fun DecimalFormat.isValidSymbols(text: String): Boolean { - return checkDecimalSeparatorDuplicate(text) && checkGroupingSeparator(text) + return checkDecimalSeparatorDuplicate(text) } // region preview diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index ff2bf968ae..59128a93a9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -38,6 +38,7 @@ fun SimpleTextField( keyboardActions: KeyboardActions = KeyboardActions.Default, color: Color = TangemTheme.colors.text.primary1, textStyle: TextStyle = TangemTheme.typography.body2.copy(color = color), + placeholderColor: Color = TangemTheme.colors.text.disabled, readOnly: Boolean = false, isValuePasted: Boolean = false, onValuePastedTriggerDismiss: () -> Unit = {}, @@ -108,6 +109,7 @@ fun SimpleTextField( value = value, textStyle = textStyle, textValue = textValue, + color = placeholderColor, ) }, modifier = modifier @@ -122,6 +124,7 @@ private fun SimpleTextPlaceholder( value: String, textStyle: TextStyle, textValue: @Composable () -> Unit, + color: Color = TangemTheme.colors.text.disabled, ) { Box { if (value.isBlank() && placeholder != null) { @@ -132,7 +135,7 @@ private fun SimpleTextPlaceholder( Text( text = it.resolveReference(), style = textStyle, - color = TangemTheme.colors.text.disabled, + color = color, ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt index 6d339e48dd..c572bb3f83 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt @@ -51,12 +51,14 @@ fun Notification( modifier: Modifier = Modifier, containerColor: Color? = null, iconTint: Color? = null, + isEnabled: Boolean = true, ) { BaseContainer( buttonsState = config.buttonsState, onClick = config.onClick, modifier = modifier, containerColor = containerColor, + isEnabled = isEnabled, ) { Column( modifier = Modifier.padding(all = TangemTheme.dimens.spacing12), @@ -67,15 +69,16 @@ fun Notification( iconTint = iconTint, title = config.title, subtitle = config.subtitle, - isClickableComponent = config.onClick != null, + isClickableComponent = isEnabled && config.onClick != null, ) - Buttons(state = config.buttonsState) + Buttons(state = config.buttonsState, isEnabled = isEnabled) } CloseableIconButton( onClick = config.onCloseClick, modifier = Modifier.align(alignment = Alignment.TopEnd), + isEnabled = isEnabled, ) } } @@ -85,6 +88,7 @@ private fun BaseContainer( buttonsState: NotificationConfig.ButtonsState?, onClick: (() -> Unit)?, modifier: Modifier = Modifier, + isEnabled: Boolean = true, containerColor: Color? = null, content: @Composable BoxScope.() -> Unit, ) { @@ -101,7 +105,7 @@ private fun BaseContainer( modifier = modifier .defaultMinSize(minHeight = TangemTheme.dimens.size62) .fillMaxWidth(), - enabled = onClick != null, + enabled = onClick != null && isEnabled, shape = TangemTheme.shapes.roundedCornersXMedium, color = containerColor ?: tempContainerColor, ) { @@ -179,27 +183,31 @@ private fun TextsBlock(title: TextReference, subtitle: TextReference) { } @Composable -private fun Buttons(state: NotificationButtonsState?) { +private fun Buttons(state: NotificationButtonsState?, isEnabled: Boolean = true) { when (state) { - is NotificationButtonsState.SecondaryButtonConfig -> SingleSecondaryButton(config = state) - is NotificationButtonsState.PrimaryButtonConfig -> SinglePrimaryButton(config = state) - is NotificationButtonsState.PairButtonsConfig -> PairButtons(config = state) + is NotificationButtonsState.SecondaryButtonConfig -> SingleSecondaryButton( + config = state, + isEnabled = isEnabled, + ) + is NotificationButtonsState.PrimaryButtonConfig -> SinglePrimaryButton(config = state, isEnabled = isEnabled) + is NotificationButtonsState.PairButtonsConfig -> PairButtons(config = state, isEnabled = isEnabled) null -> Unit } } @Composable -private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButtonConfig) { +private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButtonConfig, isEnabled: Boolean = true) { SecondaryButton( text = config.text.resolveReference(), onClick = config.onClick, modifier = Modifier.fillMaxWidth(), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) } @Composable -private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonConfig) { +private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonConfig, isEnabled: Boolean = true) { if (config.iconResId != null) { PrimaryButtonIconEnd( text = config.text.resolveReference(), @@ -207,6 +215,7 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo onClick = config.onClick, modifier = Modifier.fillMaxWidth(), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) } else { PrimaryButton( @@ -214,18 +223,20 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo onClick = config.onClick, modifier = Modifier.fillMaxWidth(), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) } } @Composable -private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) { +private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig, isEnabled: Boolean = true) { Row(horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8)) { SecondaryButton( text = config.secondaryText.resolveReference(), onClick = config.onSecondaryClick, modifier = Modifier.weight(weight = 1f), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) PrimaryButton( @@ -233,12 +244,13 @@ private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) { onClick = config.onPrimaryClick, modifier = Modifier.weight(weight = 1f), size = TangemButtonSize.WideAction, + enabled = isEnabled, ) } } @Composable -private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier) { +private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier, isEnabled: Boolean = true) { AnimatedVisibility(visible = onClick != null, modifier = modifier) { onClick ?: return@AnimatedVisibility @@ -255,6 +267,7 @@ private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Mod interactionSource = remember { MutableInteractionSource() }, indication = LocalIndication.current, role = Role.Button, + enabled = isEnabled, onClick = onClick, ), ) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt index 00945dbb44..076c60da41 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt @@ -49,7 +49,11 @@ fun NotificationWithBackground(config: NotificationConfig, modifier: Modifier = modifier = modifier .defaultMinSize(minHeight = TangemTheme.dimens.size62) .fillMaxWidth() - .clip(TangemTheme.shapes.roundedCornersXMedium), + .clip(TangemTheme.shapes.roundedCornersXMedium) + .clickable( + enabled = config.onClick != null, + onClick = config.onClick ?: {}, + ), ) { val (iconRef, titleRef, subtitleRef, closeIconRef, buttonRef, backgroundRef) = createRefs() diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/TravalaNotificationWithBackground.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/TravalaNotificationWithBackground.kt new file mode 100644 index 0000000000..9e6ba3db1d --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/TravalaNotificationWithBackground.kt @@ -0,0 +1,215 @@ +package com.tangem.core.ui.components.notifications + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.ScaleFactor +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.LineBreak +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.R +import com.tangem.core.ui.components.SpacerH8 +import com.tangem.core.ui.components.buttons.common.TangemButton +import com.tangem.core.ui.components.buttons.common.TangemButtonColors +import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.res.TangemColorPalette.White +import com.tangem.core.ui.res.TangemTheme + +/** + * Travala notification with image background + * @see Travala Promo + */ +@Suppress("LongMethod", "DestructuringDeclarationWithTooManyEntries") +@Composable +fun TravalaNotificationWithBackground(config: NotificationConfig, modifier: Modifier = Modifier) { + val button = config.buttonsState as? NotificationConfig.ButtonsState.SecondaryButtonConfig + + Box( + modifier = modifier + .defaultMinSize(minHeight = TangemTheme.dimens.size62) + .fillMaxWidth() + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(Color.Black) + .clickable( + enabled = config.onClick != null, + onClick = config.onClick ?: {}, + ), + propagateMinConstraints = true, + contentAlignment = Alignment.TopStart, + ) { + val density = LocalDensity.current + Image( + painter = painterResource(R.drawable.img_travala_banner_promo_background), + contentDescription = null, + contentScale = TravalaBackgroundScale(density), + alignment = Alignment.TopStart, + modifier = Modifier + .matchParentSize() + .wrapContentSize(unbounded = true, align = Alignment.TopStart) + .align(Alignment.TopStart), + ) + Image( + painter = painterResource(R.drawable.img_travala_banner_promo_background_2), + contentDescription = null, + contentScale = TravalaBackgroundScale(density), + alignment = Alignment.TopStart, + modifier = Modifier + .matchParentSize() + .wrapContentSize(unbounded = true, align = Alignment.TopEnd) + .align(Alignment.TopEnd), + ) + Column { + Row { + Box(modifier = Modifier.size(87.dp)) + Column( + Modifier + .weight(1f) + .padding(top = TangemTheme.dimens.spacing12), + ) { + Text( + text = config.title.resolveReference(), + style = TangemTheme.typography.button.copy( + lineBreak = LineBreak.Heading, + ), + color = TangemTheme.colors.text.constantWhite, + ) + SpacerH8() + Text( + text = formatSubtitle(config.subtitle.resolveReference()), + style = TangemTheme.typography.caption2.copy( + lineBreak = LineBreak.Heading, + ), + color = TangemTheme.colors.text.constantWhite, + ) + } + Icon( + painter = painterResource(id = R.drawable.ic_close_24), + contentDescription = null, + tint = TangemTheme.colors.text.constantWhite, + modifier = Modifier + .padding( + top = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing12, + start = TangemTheme.dimens.spacing2, + ) + .size(TangemTheme.dimens.size16) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(bounded = false), + ) { + config.onCloseClick?.invoke() + }, + ) + } + + TangemButton( + text = button?.text?.resolveReference().orEmpty(), + icon = TangemButtonIconPosition.None, + onClick = button?.onClick ?: {}, + colors = TangemButtonColors( + backgroundColor = White.copy(alpha = 0.3f), + contentColor = White, + disabledBackgroundColor = TangemTheme.colors.button.disabled, + disabledContentColor = TangemTheme.colors.text.disabled, + ), + enabled = true, + showProgress = false, + modifier = Modifier + .padding(TangemTheme.dimens.spacing12) + .fillMaxWidth(), + ) + } + } +} + +private const val TRAVALA_BACKGROUND_SRC_IMG_SCALE = 4 + +private class TravalaBackgroundScale( + val density: Density, +) : ContentScale { + override fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor { + with(density) { + val originalWidth = (srcSize.width / TRAVALA_BACKGROUND_SRC_IMG_SCALE).dp.toPx() + val widthScale = originalWidth / srcSize.width + val originalHeight = (srcSize.height / TRAVALA_BACKGROUND_SRC_IMG_SCALE).dp.toPx() + val heightScale = originalHeight / srcSize.height + return ScaleFactor(widthScale, heightScale) + } + } +} + +@Composable +private fun formatSubtitle(subtitle: String): AnnotatedString { + val pattern = Regex("\\*\\*(.*?)\\*\\*") + var startIndex = 0 + val annotatedString = buildAnnotatedString { + pattern.findAll(subtitle).forEach { matchResult -> + val index = matchResult.range.first + val matchedValue = matchResult.groups[1]?.value ?: "" + + // appends unformatted part + append(subtitle.substring(startIndex, index)) + + // applies style on ^^-wrapped parts + withStyle(SpanStyle(fontWeight = TangemTheme.typography.caption1.fontWeight)) { + append(matchedValue) + } + + // goes to next part + startIndex = matchResult.range.last + 1 + } + + // appends remaining ending if exists + append(subtitle.substring(startIndex)) + } + + return annotatedString +} + +//region preview +@Preview +@Composable +private fun TravalaNotificationWithBackgroundPreview() { + TangemTheme { + TravalaNotificationWithBackground( + config = NotificationConfig( + title = resourceReference( + id = R.string.main_travala_promotion_title, + ), + subtitle = resourceReference( + id = R.string.main_travala_promotion_description, + formatArgs = wrappedList("May 13", "June 12"), + ), + iconResId = R.drawable.img_swap_promo, + backgroundResId = R.drawable.img_travala_banner_promo_background, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(id = R.string.token_swap_promotion_button), + onClick = {}, + ), + ), + ) + } +} +//endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt new file mode 100644 index 0000000000..408e9130ee --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/MarkdownExtension.kt @@ -0,0 +1,56 @@ +package com.tangem.core.ui.extensions + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +import org.intellij.markdown.MarkdownElementTypes +import org.intellij.markdown.ast.ASTNode +import org.intellij.markdown.ast.getTextInNode +import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor +import org.intellij.markdown.parser.MarkdownParser + +/** Markdown parser */ +@Composable +fun rememberMarkdownParser() = remember { + MarkdownParser(CommonMarkFlavourDescriptor()) +} + +/** + * Styling markdown tree recursively + * + * @param markdownText original text + * @param node current processed node + */ +@Composable +fun AnnotatedString.Builder.appendMarkdown(markdownText: String, node: ASTNode): AnnotatedString.Builder { + when (node.type) { + MarkdownElementTypes.MARKDOWN_FILE, MarkdownElementTypes.PARAGRAPH -> { + node.children.forEach { childNode -> + appendMarkdown( + markdownText = markdownText, + node = childNode, + ) + } + } + MarkdownElementTypes.STRONG -> { + withStyle(SpanStyle(fontWeight = FontWeight.Medium)) { + node.children + .drop(2) + .dropLast(2) + .forEach { childNode -> + appendMarkdown( + markdownText = markdownText, + node = childNode, + ) + } + } + } + else -> { + append(node.getTextInNode(markdownText).toString()) + } + } + return this +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt index 766815d328..2f22994f64 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt @@ -8,6 +8,9 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.buildAnnotatedString +import org.intellij.markdown.MarkdownElementTypes /** * Utility class for creating text as [String] or [StringRes]. @@ -160,6 +163,29 @@ fun TextReference.resolveReference(resources: Resources): String { } } +/** Resolve [TextReference] as [AnnotatedString] */ +@Composable +fun TextReference.resolveAnnotatedReference(): AnnotatedString { + return when (this) { + is TextReference.Res -> { + val args = formatArgs + .map { if (it is TextReference) it.resolveReference() else it } + .toTypedArray() + + formatAnnotated(stringResource(id = id, *args)) + } + is TextReference.PluralRes -> formatAnnotated( + pluralStringResource(id, count, *formatArgs.toTypedArray()), + ) + is TextReference.Str -> formatAnnotated(value) + is TextReference.Combined -> buildAnnotatedString { + refs.forEach { + append(formatAnnotated(it.resolveReference())) + } + } + } +} + /** Concatenate [this] reference with [ref] */ operator fun TextReference.plus(ref: TextReference): TextReference { return when (this) { @@ -169,4 +195,14 @@ operator fun TextReference.plus(ref: TextReference): TextReference { is TextReference.Str, -> TextReference.Combined(refs = wrappedList(this, ref)) } +} + +@Composable +private fun formatAnnotated(rawString: String): AnnotatedString { + val markdownDescriptor = rememberMarkdownParser() + val parsedTree = markdownDescriptor.parse(MarkdownElementTypes.MARKDOWN_FILE, rawString, true) + + return buildAnnotatedString { + appendMarkdown(markdownText = rawString, node = parsedTree) + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt index 0ea40469e2..d42121d3f0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.utils +import android.text.format.DateFormat import org.joda.time.DateTime import org.joda.time.format.DateTimeFormat import org.joda.time.format.DateTimeFormatter @@ -55,6 +56,16 @@ object DateTimeFormatters { .withLocale(Locale.getDefault()) } + /** + * In API version < 24, there may be some problems with getting the best date and time format pattern. + */ + val dateMMMMd: DateTimeFormatter by lazy { + DateTimeFormatterBuilder() + .appendPattern(DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMMM d")) + .toFormatter() + .withLocale(Locale.getDefault()) + } + val dateTimeFormatter: DateTimeFormatter by lazy { DateTimeFormat.forPattern("dd.MM.yyyy HH:mm") } diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt index 5006df1d7a..2d685763e3 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt @@ -10,9 +10,9 @@ import java.text.DecimalFormatSymbols import java.util.Locale private const val TEXT_CHUNK_THOUSAND = 3 -private const val POINT_SEPARATOR = '.' -private const val COMMA_SEPARATOR = ',' private const val SCIENTIFIC_NOTATION = 'e' +const val POINT_SEPARATOR = '.' +const val COMMA_SEPARATOR = ',' const val DECIMAL_SEPARATOR_LIMIT = 1 @Composable diff --git a/core/ui/src/main/res/drawable/img_travala_banner_promo_background.webp b/core/ui/src/main/res/drawable/img_travala_banner_promo_background.webp new file mode 100644 index 0000000000..bc3845b8b7 Binary files /dev/null and b/core/ui/src/main/res/drawable/img_travala_banner_promo_background.webp differ diff --git a/core/ui/src/main/res/drawable/img_travala_banner_promo_background_2.webp b/core/ui/src/main/res/drawable/img_travala_banner_promo_background_2.webp new file mode 100644 index 0000000000..c051b16418 Binary files /dev/null and b/core/ui/src/main/res/drawable/img_travala_banner_promo_background_2.webp differ diff --git a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt index 3121a961d7..a92a4f5eb9 100644 --- a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt +++ b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt @@ -23,7 +23,17 @@ internal class DefaultPromoRepository( }.getOrNull() } + override suspend fun getTravalaPromoBanner(): PromoBanner? { + return runCatching(dispatchers.io) { + promoResponseConverter.convert( + tangemApi.getPromotionInfo(TRAVALA) + .getOrThrow(), + ) + }.getOrNull() + } + private companion object { private const val CHANGELLY_NAME = "changelly" + private const val TRAVALA = "travala" } } \ No newline at end of file diff --git a/data/promo/src/main/java/com/tangem/data/promo/converters/PromoResponseConverter.kt b/data/promo/src/main/java/com/tangem/data/promo/converters/PromoResponseConverter.kt index c7ad8f331a..151675506b 100644 --- a/data/promo/src/main/java/com/tangem/data/promo/converters/PromoResponseConverter.kt +++ b/data/promo/src/main/java/com/tangem/data/promo/converters/PromoResponseConverter.kt @@ -13,6 +13,7 @@ class PromoResponseConverter : Converter { name = value.name, bannerState = PromoBanner.BannerState( status = bannerState.status, + link = bannerState.link, timeline = PromoBanner.Timeline( start = DateTime.parse(bannerState.timeline.start), end = DateTime.parse(bannerState.timeline.end), diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultSwapPromoRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt similarity index 55% rename from data/settings/src/main/java/com/tangem/data/settings/DefaultSwapPromoRepository.kt rename to data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt index 6d9ccf740a..80ba44157d 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/DefaultSwapPromoRepository.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt @@ -3,36 +3,48 @@ package com.tangem.data.settings import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_TRAVALA_PROMO_SHOWN_KEY import com.tangem.datasource.local.preferences.utils.get import com.tangem.datasource.local.preferences.utils.store -import com.tangem.domain.settings.repositories.SwapPromoRepository +import com.tangem.domain.settings.repositories.PromoSettingsRepository import kotlinx.coroutines.flow.Flow /** * Repository for showing swap promo notification. */ -class DefaultSwapPromoRepository( +class DefaultPromoSettingsRepository( private val appPreferencesStore: AppPreferencesStore, -) : SwapPromoRepository { - override fun isReadyToShowWalletPromo(): Flow { +) : PromoSettingsRepository { + override fun isReadyToShowWalletSwapPromo(): Flow { return appPreferencesStore.get(IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY, true) } - override fun isReadyToShowTokenPromo(): Flow { + override fun isReadyToShowTokenSwapPromo(): Flow { return appPreferencesStore.get(IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY, true) } - override suspend fun setNeverToShowWalletPromo() { + override suspend fun setNeverToShowWalletSwapPromo() { appPreferencesStore.store( key = IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY, value = false, ) } - override suspend fun setNeverToShowTokenPromo() { + override suspend fun setNeverToShowTokenSwapPromo() { appPreferencesStore.store( key = IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY, value = false, ) } + + override fun isReadyToShowWalletTravalaPromo(): Flow { + return appPreferencesStore.get(IS_WALLET_TRAVALA_PROMO_SHOWN_KEY, true) + } + + override suspend fun setNeverToShowWalletTravalaPromo() { + appPreferencesStore.store( + key = IS_WALLET_TRAVALA_PROMO_SHOWN_KEY, + value = false, + ) + } } \ No newline at end of file diff --git a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt index 6211aace4f..4f6bf8e17b 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt @@ -2,11 +2,11 @@ package com.tangem.data.settings.di import com.tangem.data.settings.DefaultAppRatingRepository import com.tangem.data.settings.DefaultSettingsRepository -import com.tangem.data.settings.DefaultSwapPromoRepository +import com.tangem.data.settings.DefaultPromoSettingsRepository import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.settings.repositories.AppRatingRepository import com.tangem.domain.settings.repositories.SettingsRepository -import com.tangem.domain.settings.repositories.SwapPromoRepository +import com.tangem.domain.settings.repositories.PromoSettingsRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -31,7 +31,7 @@ internal object SettingsDataModule { @Provides @Singleton - fun provideSwapPromoRepository(appPreferencesStore: AppPreferencesStore): SwapPromoRepository { - return DefaultSwapPromoRepository(appPreferencesStore = appPreferencesStore) + fun providePromoSettingsSettingsRepository(appPreferencesStore: AppPreferencesStore): PromoSettingsRepository { + return DefaultPromoSettingsRepository(appPreferencesStore = appPreferencesStore) } } \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt new file mode 100644 index 0000000000..dc118e8f15 --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/DeleteSavedAccessCodesUseCase.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.card + +import arrow.core.Either + +interface DeleteSavedAccessCodesUseCase { + + suspend operator fun invoke(cardId: String): Either +} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/CardTypesResolver.kt b/domain/legacy/src/main/java/com/tangem/domain/common/CardTypesResolver.kt index 0cbee25c28..56a874bd6a 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/CardTypesResolver.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/CardTypesResolver.kt @@ -30,6 +30,12 @@ interface CardTypesResolver { fun isSatoshiFriendsWallet(): Boolean + fun isBitcoinPizzaDayWallet(): Boolean + + fun isVeChainWallet(): Boolean + + fun isNewWorldEliteWallet(): Boolean + fun isWhiteWallet(): Boolean fun isWallet2(): Boolean diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt b/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt index 077ffe5de1..8c3a6ec485 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt @@ -48,6 +48,12 @@ internal class TangemCardTypesResolver( override fun isSatoshiFriendsWallet(): Boolean = card.batchId == SATOSHI_WALLET_BATCH_ID + override fun isBitcoinPizzaDayWallet(): Boolean = card.batchId == BITCOIN_PIZZA_DAY_WALLET_BATCH_ID + + override fun isVeChainWallet(): Boolean = card.batchId == VECHAIN_WALLET_BATCH_ID + + override fun isNewWorldEliteWallet(): Boolean = card.batchId == NEW_WORLD_ELITE_WALLET_BATCH_ID + override fun isWhiteWallet(): Boolean { return walletData == null && card.firmwareVersion <= FirmwareVersion.HDWalletAvailable } @@ -154,5 +160,8 @@ internal class TangemCardTypesResolver( const val WHITE_WALLET2_BATCH_ID = "AF15" const val TRILLIANT_WALLET_BATCH_ID = "AF16" const val AVRORA_WALLET_BATCH_ID = "AF18" + const val BITCOIN_PIZZA_DAY_WALLET_BATCH_ID = "AF33" + const val VECHAIN_WALLET_BATCH_ID = "AF29" + const val NEW_WORLD_ELITE_WALLET_BATCH_ID = "AF26" } } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoTokenUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoTokenUseCase.kt index 966e1bcf83..8ede57bcf9 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoTokenUseCase.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoTokenUseCase.kt @@ -1,11 +1,11 @@ package com.tangem.domain.settings -import com.tangem.domain.settings.repositories.SwapPromoRepository +import com.tangem.domain.settings.repositories.PromoSettingsRepository import kotlinx.coroutines.flow.Flow -class ShouldShowSwapPromoTokenUseCase(private val swapPromoRepository: SwapPromoRepository) { +class ShouldShowSwapPromoTokenUseCase(private val promoSettingsRepository: PromoSettingsRepository) { - operator fun invoke(): Flow = swapPromoRepository.isReadyToShowTokenPromo() + operator fun invoke(): Flow = promoSettingsRepository.isReadyToShowTokenSwapPromo() - suspend fun neverToShow() = swapPromoRepository.setNeverToShowTokenPromo() + suspend fun neverToShow() = promoSettingsRepository.setNeverToShowTokenSwapPromo() } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoWalletUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoWalletUseCase.kt index e2b30e1ba5..233277f71f 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoWalletUseCase.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoWalletUseCase.kt @@ -1,11 +1,11 @@ package com.tangem.domain.settings -import com.tangem.domain.settings.repositories.SwapPromoRepository +import com.tangem.domain.settings.repositories.PromoSettingsRepository import kotlinx.coroutines.flow.Flow -class ShouldShowSwapPromoWalletUseCase(private val swapPromoRepository: SwapPromoRepository) { +class ShouldShowSwapPromoWalletUseCase(private val promoSettingsRepository: PromoSettingsRepository) { - operator fun invoke(): Flow = swapPromoRepository.isReadyToShowWalletPromo() + operator fun invoke(): Flow = promoSettingsRepository.isReadyToShowWalletSwapPromo() - suspend fun neverToShow() = swapPromoRepository.setNeverToShowWalletPromo() + suspend fun neverToShow() = promoSettingsRepository.setNeverToShowWalletSwapPromo() } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowTravalaPromoWalletUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowTravalaPromoWalletUseCase.kt new file mode 100644 index 0000000000..643e7387c8 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowTravalaPromoWalletUseCase.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.settings + +import com.tangem.domain.settings.repositories.PromoSettingsRepository +import kotlinx.coroutines.flow.Flow + +class ShouldShowTravalaPromoWalletUseCase(private val promoSettingsRepository: PromoSettingsRepository) { + + operator fun invoke(): Flow = promoSettingsRepository.isReadyToShowWalletTravalaPromo() + + suspend fun neverToShow() = promoSettingsRepository.setNeverToShowWalletTravalaPromo() +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/PromoSettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/PromoSettingsRepository.kt new file mode 100644 index 0000000000..0e3946bb89 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/PromoSettingsRepository.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.settings.repositories + +import kotlinx.coroutines.flow.Flow + +interface PromoSettingsRepository { + fun isReadyToShowWalletSwapPromo(): Flow + + fun isReadyToShowTokenSwapPromo(): Flow + + suspend fun setNeverToShowWalletSwapPromo() + + suspend fun setNeverToShowTokenSwapPromo() + + fun isReadyToShowWalletTravalaPromo(): Flow + + suspend fun setNeverToShowWalletTravalaPromo() +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SwapPromoRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SwapPromoRepository.kt deleted file mode 100644 index ca97755e66..0000000000 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SwapPromoRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.domain.settings.repositories - -import kotlinx.coroutines.flow.Flow - -interface SwapPromoRepository { - fun isReadyToShowWalletPromo(): Flow - - fun isReadyToShowTokenPromo(): Flow - - suspend fun setNeverToShowWalletPromo() - - suspend fun setNeverToShowTokenPromo() -} \ No newline at end of file diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts index c0a73ff9fd..501cf70dd8 100644 --- a/domain/tokens/build.gradle.kts +++ b/domain/tokens/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { /** Project - Other */ implementation(projects.core.utils) + implementation(projects.libs.crypto) /** Android - Other */ implementation(deps.androidx.paging.runtime) diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/promo/PromoBanner.kt b/domain/tokens/models/src/main/java/com/tangem/domain/promo/PromoBanner.kt index 564a49e3d4..2608085143 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/promo/PromoBanner.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/promo/PromoBanner.kt @@ -12,6 +12,7 @@ data class PromoBanner( data class BannerState( val timeline: Timeline, val status: String, + val link: String?, ) data class Timeline( diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt index 5c1cad5f44..86b0059faf 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt @@ -45,4 +45,6 @@ sealed class CryptoCurrencyWarning { val startDateTime: DateTime, val endDateTime: DateTime, ) : CryptoCurrencyWarning() + + data object BeaconChainShutdown : CryptoCurrencyWarning() } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index 43a7a2fda8..5036e8f341 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -12,6 +12,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.runCatching import com.tangem.utils.isNullOrZero @@ -74,6 +75,7 @@ class GetCurrencyWarningsUseCase( *coinRelatedWarnings.toTypedArray(), getNetworkUnavailableWarning(currencyStatus), getNetworkNoAccountWarning(currencyStatus), + getBeaconChainShutdownWarning(currency.network.id), ) }.flowOn(dispatchers.io) } @@ -261,6 +263,10 @@ class GetCurrencyWarningsUseCase( } } + private fun getBeaconChainShutdownWarning(networkId: Network.ID): CryptoCurrencyWarning.BeaconChainShutdown? { + return if (BlockchainUtils.isBeaconChain(networkId.value)) CryptoCurrencyWarning.BeaconChainShutdown else null + } + private fun BigDecimal?.isZero(): Boolean { return this?.signum() == 0 } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt new file mode 100644 index 0000000000..0cc768a333 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt @@ -0,0 +1,24 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class GetNetworkAddressesUseCase( + internal val networksRepository: NetworksRepository, +) { + + operator fun invoke(userWalletId: UserWalletId, network: Network): Flow = + networksRepository.getNetworkStatusesUpdates(userWalletId, setOf(network)) + .map { networkStatuses -> + when (val networkStatus = networkStatuses.singleOrNull { it.network.id == network.id }?.value) { + is NetworkStatus.NoAccount -> networkStatus.address.defaultAddress.value + is NetworkStatus.Unreachable -> networkStatus.address?.defaultAddress?.value.orEmpty() + is NetworkStatus.Verified -> networkStatus.address.defaultAddress.value + else -> "" + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt index b15e05db54..ed42ab1cfc 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt @@ -5,4 +5,6 @@ import com.tangem.domain.promo.PromoBanner interface PromoRepository { suspend fun getChangellyPromoBanner(): PromoBanner? + + suspend fun getTravalaPromoBanner(): PromoBanner? } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt index 76872f50ff..4045a3a98a 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetWalletsUseCase.kt @@ -3,6 +3,7 @@ package com.tangem.domain.wallets.usecase import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWallet import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull /** * Use case for getting list of user wallets @@ -15,4 +16,7 @@ class GetWalletsUseCase(private val userWalletsListManager: UserWalletsListManag @Throws(IllegalArgumentException::class) operator fun invoke(): Flow> = userWalletsListManager.userWallets + + @Throws(IllegalArgumentException::class) + suspend fun invokeSync(): List? = userWalletsListManager.userWallets.firstOrNull() } \ No newline at end of file diff --git a/features/send/api/src/main/kotlin/com/tangem/features/send/api/featuretoggles/SendFeatureToggles.kt b/features/send/api/src/main/kotlin/com/tangem/features/send/api/featuretoggles/SendFeatureToggles.kt index d80f08cf56..453a79b60c 100644 --- a/features/send/api/src/main/kotlin/com/tangem/features/send/api/featuretoggles/SendFeatureToggles.kt +++ b/features/send/api/src/main/kotlin/com/tangem/features/send/api/featuretoggles/SendFeatureToggles.kt @@ -7,4 +7,7 @@ interface SendFeatureToggles { /** Availability of redesigned send screen */ val isRedesignedSendEnabled: Boolean + + /** Updates remote toggle */ + suspend fun fetchNewSendEnabled() } \ No newline at end of file diff --git a/features/send/impl/build.gradle.kts b/features/send/impl/build.gradle.kts index 43a930c199..66e103efad 100644 --- a/features/send/impl/build.gradle.kts +++ b/features/send/impl/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(projects.core.navigation) implementation(projects.core.analytics) implementation(projects.core.analytics.models) + implementation(projects.core.datasource) /** Common */ implementation(projects.common) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendFeatureTogglesModule.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendFeatureTogglesModule.kt index 6b1465762c..60c8b2822c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendFeatureTogglesModule.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendFeatureTogglesModule.kt @@ -1,8 +1,10 @@ package com.tangem.features.send.impl.di import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.features.send.impl.featuretoggles.DefaultSendFeatureToggles +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -18,7 +20,15 @@ internal object SendFeatureTogglesModule { @Provides @Singleton - fun provideSendFeatureToggles(featureTogglesManager: FeatureTogglesManager): SendFeatureToggles { - return DefaultSendFeatureToggles(featureTogglesManager = featureTogglesManager) + fun provideSendFeatureToggles( + featureTogglesManager: FeatureTogglesManager, + tangemTechApi: TangemTechApi, + dispatchers: CoroutineDispatcherProvider, + ): SendFeatureToggles { + return DefaultSendFeatureToggles( + featureTogglesManager = featureTogglesManager, + tangemTechApi = tangemTechApi, + dispatchers = dispatchers, + ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/featuretoggles/DefaultSendFeatureToggles.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/featuretoggles/DefaultSendFeatureToggles.kt index 71f11c480d..b36cba4cbd 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/featuretoggles/DefaultSendFeatureToggles.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/featuretoggles/DefaultSendFeatureToggles.kt @@ -1,16 +1,41 @@ package com.tangem.features.send.impl.featuretoggles import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.datasource.api.common.response.getOrThrow +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.features.send.api.featuretoggles.SendFeatureToggles +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runCatching +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import timber.log.Timber /** * Default implementation of Send feature toggles * * @property featureTogglesManager manager for getting information about the availability of feature toggles + * @property tangemTechApi api to get remote feature toggle for send + * @property dispatchers coroutine dispatchers */ internal class DefaultSendFeatureToggles( private val featureTogglesManager: FeatureTogglesManager, + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, ) : SendFeatureToggles { + + private val remoteSendEnabled: MutableStateFlow = MutableStateFlow(true) + override val isRedesignedSendEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_SEND_SCREEN_ENABLED") + get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_SEND_SCREEN_ENABLED") && + remoteSendEnabled.value + + override suspend fun fetchNewSendEnabled() { + runCatching(dispatchers.io) { + tangemTechApi.getFeatures().getOrThrow() + }.onSuccess { response -> + remoteSendEnabled.update { response.isNewSendEnabled } + }.onFailure { + Timber.e(it.localizedMessage, "Unable to fetch new send toggle") + } + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt index feca8a0786..c4ce202599 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/SendAnalyticEvents.kt @@ -1,7 +1,9 @@ package com.tangem.features.send.impl.presentation.analytics import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN +import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN import com.tangem.core.analytics.models.AnalyticsParam.Key.TYPE @@ -66,9 +68,9 @@ internal sealed class SendAnalyticEvents( data object FeeScreenOpened : SendAnalyticEvents(event = "Fee Screen Opened") /** Selected fee (send after next screen opened) */ - data class SelectedFee(val feeType: SelectedFeeType) : SendAnalyticEvents( + data class SelectedFee(val feeType: AnalyticsParam.FeeType) : SendAnalyticEvents( event = "Fee Selected", - params = mapOf("Fee Type" to feeType.name), + params = mapOf("Fee Type" to feeType.value), ) /** Custom fee selected */ @@ -97,7 +99,16 @@ internal sealed class SendAnalyticEvents( // region Transaction Result /** Transaction send screen opened */ - data object TransactionScreenOpened : SendAnalyticEvents(event = "Transaction Sent Screen Opened") + data class TransactionScreenOpened( + val token: String, + val feeType: AnalyticsParam.FeeType, + ) : SendAnalyticEvents( + event = "Transaction Sent Screen Opened", + params = mapOf( + TOKEN to token, + FEE_TYPE to feeType.value, + ), + ) /** Share button clicked */ data object ShareButtonClicked : SendAnalyticEvents(event = "Button - Share") @@ -145,12 +156,4 @@ internal enum class EnterAddressSource { internal enum class SelectedCurrencyType(val value: String) { Token("Token"), AppCurrency("App Currency"), -} - -internal enum class SelectedFeeType { - Min, - Max, - Fixed, - Normal, - Custom, } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt index 27c93b9743..ac6485fc8a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/analytics/utils/SendScreenAnalyticSender.kt @@ -2,8 +2,10 @@ package com.tangem.features.send.impl.presentation.analytics.utils import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.Basic +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.send.impl.presentation.analytics.SelectedCurrencyType -import com.tangem.features.send.impl.presentation.analytics.SelectedFeeType import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents import com.tangem.features.send.impl.presentation.analytics.SendScreenSource import com.tangem.features.send.impl.presentation.state.SendUiState @@ -11,11 +13,13 @@ import com.tangem.features.send.impl.presentation.state.SendUiStateType import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeType +import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.utils.Provider internal class SendScreenAnalyticSender( private val stateRouterProvider: Provider, private val currentStateProvider: Provider, + private val cryptoCurrencyProvider: Provider, private val analyticsEventHandler: AnalyticsEventHandler, ) { fun send(prevScreen: SendUiStateType, state: SendUiState) { @@ -73,16 +77,57 @@ internal class SendScreenAnalyticSender( ) } + fun sendTransaction() { + val state = currentStateProvider() + val isEditState = stateRouterProvider().isEditState + val cryptoCurrency = cryptoCurrencyProvider() + val feeState = state.getFeeState(isEditState) ?: return + val recipientState = state.getRecipientState(isEditState) ?: return + + val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return + val feeType = getSendTransactionFeeType(feeSelectorState) + analyticsEventHandler.send( + SendAnalyticEvents.TransactionScreenOpened( + token = cryptoCurrency.symbol, + feeType = feeType, + ), + ) + analyticsEventHandler.send( + Basic.TransactionSent( + sentFrom = AnalyticsParam.TxSentFrom.Send( + blockchain = cryptoCurrency.network.name, + token = cryptoCurrency.symbol, + feeType = feeType, + ), + memoType = getSendTransactionMemoType(recipientState.memoTextField), + ), + ) + } + private fun sendSelectedFeeAnalytics(feeSelectorState: FeeSelectorState.Content) { - val type = when (feeSelectorState.fees) { - is TransactionFee.Single -> SelectedFeeType.Fixed - is TransactionFee.Choosable -> when (feeSelectorState.selectedFee) { - FeeType.Slow -> SelectedFeeType.Min - FeeType.Market -> SelectedFeeType.Normal - FeeType.Fast -> SelectedFeeType.Max - FeeType.Custom -> SelectedFeeType.Custom - } - } + val type = getSendTransactionFeeType(feeSelectorState) analyticsEventHandler.send(SendAnalyticEvents.SelectedFee(type)) } + + private fun getSendTransactionFeeType(feeSelectorState: FeeSelectorState.Content): AnalyticsParam.FeeType = + when (feeSelectorState.fees) { + is TransactionFee.Single -> AnalyticsParam.FeeType.Fixed + is TransactionFee.Choosable -> when (feeSelectorState.selectedFee) { + FeeType.Slow -> AnalyticsParam.FeeType.Min + FeeType.Market -> AnalyticsParam.FeeType.Normal + FeeType.Fast -> AnalyticsParam.FeeType.Max + FeeType.Custom -> AnalyticsParam.FeeType.Custom + } + } + + private fun getSendTransactionMemoType( + recipientMemo: SendTextField.RecipientMemo?, + ): Basic.TransactionSent.MemoType { + val memo = recipientMemo?.value + return when { + memo?.isBlank() == true -> Basic.TransactionSent.MemoType.Empty + memo?.isNotBlank() == true -> Basic.TransactionSent.MemoType.Full + else -> Basic.TransactionSent.MemoType.Null + } + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt index 69e56bfc62..8a7d5e7f3a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt @@ -1,15 +1,18 @@ package com.tangem.features.send.impl.presentation.domain import androidx.compose.runtime.Immutable +import com.tangem.domain.wallets.models.UserWalletId /** * Available wallet to send * * @property name wallet name + * @property userWalletId wallet id * @property address blockchain address */ @Immutable data class AvailableWallet( val name: String, + val userWalletId: UserWalletId, val address: String, ) \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt index a111cac1e9..f2bf0e4d67 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt @@ -54,7 +54,7 @@ internal sealed class SendNotification(val config: NotificationConfig) { wrappedList(cryptoCurrency, utxoLimit, amountLimit), ), buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig( - text = resourceReference(R.string.send_notification_reduce_to, wrappedList(amountLimit)), + text = resourceReference(R.string.send_notification_leave_button, wrappedList(amountLimit)), onClick = onConfirmClick, ), ) @@ -98,7 +98,7 @@ internal sealed class SendNotification(val config: NotificationConfig) { title = resourceReference(R.string.send_notification_existential_deposit_title), subtitle = resourceReference(R.string.send_notification_existential_deposit_text, wrappedList(deposit)), buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig( - text = resourceReference(R.string.send_notification_existential_deposit_button, wrappedList(deposit)), + text = resourceReference(R.string.send_notification_leave_button, wrappedList(deposit)), onClick = onConfirmClick, ), ) @@ -119,12 +119,13 @@ internal sealed class SendNotification(val config: NotificationConfig) { ), ) { data class HighFeeError( + val currencyName: String, val amount: String, val onConfirmClick: () -> Unit, val onCloseClick: () -> Unit, ) : Warning( title = resourceReference(R.string.send_notification_high_fee_title), - subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(amount)), + subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(currencyName, amount)), buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig( text = resourceReference(R.string.send_notification_reduce_by, wrappedList(amount)), onClick = onConfirmClick, @@ -156,7 +157,7 @@ internal sealed class SendNotification(val config: NotificationConfig) { data class FeeCoverageNotification(val cryptoAmount: String, val fiatAmount: String) : Warning( title = resourceReference(R.string.send_network_fee_warning_title), subtitle = resourceReference( - R.string.send_network_fee_warning_content, + R.string.common_network_fee_warning_content, wrappedList(cryptoAmount, fiatAmount), ), ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 57bb2ccffc..377143195d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -72,11 +72,12 @@ internal class SendNotificationFactory( amountValue = amountValue, feeValue = feeValue, ) - val sendingAmount = calculateSubtractedAmount( - isFeeCoverage = isFeeCoverage, + val sendingAmount = checkAndCalculateSubtractedAmount( + isAmountSubtractAvailable = isFeeCoverage, cryptoCurrencyStatus = cryptoCurrencyStatusProvider(), amountValue = amountValue, feeValue = feeValue, + reduceAmountBy = sendState.reduceAmountBy, ) buildList { // errors @@ -204,7 +205,7 @@ internal class SendNotificationFactory( val spendingAmount = if (cryptoCurrency is CryptoCurrency.Token) { feeAmount } else { - feeAmount + receivedAmount + receivedAmount } val currencyDeposit = currencyChecksRepository.getExistentialDeposit( userWalletId, @@ -270,6 +271,7 @@ internal class SendNotificationFactory( if (!ignoreAmountReduce && isTotalBalance && isTezos) { add( SendNotification.Warning.HighFeeError( + currencyName = cryptoCurrencyStatus.currency.name, amount = threshold.toPlainString(), onConfirmClick = { clickIntents.onAmountReduceClick( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt index b06d42a23d..a8a93bcd32 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt @@ -54,7 +54,7 @@ internal fun checkFeeCoverage( /** * Calculates subtracted amount */ -internal fun calculateSubtractedAmount( +private fun calculateSubtractedAmount( isFeeCoverage: Boolean, cryptoCurrencyStatus: CryptoCurrencyStatus, amountValue: BigDecimal, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt index a2a13e987a..62bf1460ad 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt @@ -47,7 +47,7 @@ internal class BitcoinCustomFeeConverter( keyboardType = KeyboardType.Number, ), title = resourceReference(R.string.send_max_fee), - footer = resourceReference(R.string.send_max_fee_footer), + footer = resourceReference(R.string.send_bitcoin_custom_fee_footer), label = getFiatReference( rate = feeCurrency?.fiatRate, value = feeValue, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt index 6bed843250..d64f1c3d47 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt @@ -43,7 +43,7 @@ internal class EthereumCustomFeeConverter( keyboardType = KeyboardType.Number, ), title = resourceReference(R.string.send_max_fee), - footer = resourceReference(R.string.send_max_fee_footer), + footer = resourceReference(R.string.send_evm_custom_fee_footer), label = getFiatReference( rate = feeCurrency?.fiatRate, value = feeValue, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt index 7d4dfe3b45..8f3d03dbbc 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt @@ -10,6 +10,7 @@ import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import com.tangem.utils.isNullOrZero +import java.math.RoundingMode internal class SendAmountFieldMaxAmountConverter( private val stateRouterProvider: Provider, @@ -33,7 +34,7 @@ internal class SendAmountFieldMaxAmountConverter( val isDoneActionEnabled = !decimalCryptoValue.isNullOrZero() val cryptoValue = decimalCryptoValue?.parseBigDecimal(cryptoDecimals).orEmpty() - val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals).orEmpty() + val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals, roundingMode = RoundingMode.HALF_UP).orEmpty() return state.copyWrapped( isEditState = isEditState, amountState = amountState.copy( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index e858d9b150..93cc9067fd 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -26,12 +26,15 @@ import com.tangem.core.ui.components.buttons.common.TangemButton import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults import com.tangem.core.ui.components.keyboardAsState +import com.tangem.core.ui.extensions.resolveAnnotatedReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.shareText +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.SendUiStateType +import com.tangem.features.send.impl.presentation.utils.getFiatFormatted @Composable internal fun SendNavigationButtons( @@ -172,21 +175,27 @@ private fun SendingText( } if (feeFiat != null && sendingFiat != null) { - val sendingValue = BigDecimalFormatter.formatFiatAmount( - fiatAmount = sendingFiat, - fiatCurrencyCode = feeState.appCurrency.code, - fiatCurrencySymbol = feeState.appCurrency.symbol, + val sendingValue = getFiatFormatted( + value = sendingFiat, + currencySymbol = feeState.appCurrency.symbol, + currencyCode = feeState.appCurrency.code, ) - val feeValue = BigDecimalFormatter.formatFiatAmount( - fiatAmount = feeFiat, - fiatCurrencyCode = feeState.appCurrency.code, - fiatCurrencySymbol = feeState.appCurrency.symbol, + val feeValue = getFiatFormatted( + value = feeState.fee?.amount?.value, + currencySymbol = feeState.appCurrency.symbol, + currencyCode = feeState.appCurrency.code, ) + val textResource = remember(sendingValue, feeValue) { + resourceReference( + id = R.string.send_summary_transaction_description, + formatArgs = wrappedList(sendingValue, feeValue), + ) + } Text( - text = stringResource(id = R.string.send_summary_transaction_description, sendingValue, feeValue), + text = textResource.resolveAnnotatedReference(), textAlign = TextAlign.Center, - style = TangemTheme.typography.caption1, - color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.primary1, modifier = Modifier .fillMaxWidth() .padding(TangemTheme.dimens.spacing12), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index d6529709b7..819655152a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -33,7 +33,10 @@ import kotlinx.coroutines.flow.withIndex @Composable internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) { val snackbarHostState = remember { SnackbarHostState() } - BackHandler(onBack = uiState.clickIntents::onBackClick) + val onBackClick = uiState.clickIntents::onBackClick.takeIf { + uiState.sendState?.isSending != true + } ?: {} + BackHandler(onBack = onBackClick) Column( modifier = Modifier .fillMaxSize() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt index fcff795b70..929cfdf3b2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt @@ -15,6 +15,7 @@ internal fun LazyListScope.notifications( notifications: ImmutableList, modifier: Modifier = Modifier, hasPaddingAbove: Boolean = false, + isClickDisabled: Boolean = false, ) { itemsIndexed( items = notifications, @@ -44,6 +45,7 @@ internal fun LazyListScope.notifications( -> null is SendNotification.Error -> TangemTheme.colors.icon.warning }, + isEnabled = !isClickDisabled, ) }, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt index d9c609a5ca..d29fea2394 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt @@ -16,6 +16,7 @@ import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeType @@ -114,11 +115,14 @@ private fun FeeError(feeSelectorState: FeeSelectorState) { private fun FeeSelectorState.Content.getAmount(feeType: FeeType): Amount? { val choosableFees = fees as? TransactionFee.Choosable + val decimals = fees.normal.amount.decimals + val customValue = this.customValues.firstOrNull()?.value?.parseToBigDecimal(decimals) + val customAmount = fees.normal.amount.copy(value = customValue) return when (feeType) { FeeType.Slow -> choosableFees?.minimum?.amount FeeType.Market -> fees.normal.amount FeeType.Fast -> choosableFees?.priority?.amount - FeeType.Custom -> null + FeeType.Custom -> customAmount } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt index 5c0ca33a84..c59617aac0 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt @@ -33,9 +33,10 @@ internal fun TextFieldWithPaste( ) { val (title, color) = when { isError && error != null -> error to TangemTheme.colors.text.warning - isReadOnly -> label to TangemTheme.colors.text.disabled + isReadOnly -> label to TangemTheme.colors.text.tertiary else -> label to TangemTheme.colors.text.secondary } + val placeholderColor = if (isReadOnly) TangemTheme.colors.text.tertiary else TangemTheme.colors.text.disabled FooterContainer(modifier, footer) { Box( modifier = Modifier @@ -59,6 +60,7 @@ internal fun TextFieldWithPaste( SimpleTextField( value = value, placeholder = placeholder, + placeholderColor = placeholderColor, onValueChange = onValueChange, readOnly = isReadOnly, modifier = Modifier diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt index 3533b2a175..e994f44e6f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt @@ -26,7 +26,7 @@ import com.tangem.features.send.impl.presentation.state.previewdata.AmountStateP @Composable internal fun AmountBlock( amountState: SendStates.AmountState, - isSuccess: Boolean, + isClickDisabled: Boolean, isEditingDisabled: Boolean, onClick: () -> Unit, ) { @@ -55,7 +55,7 @@ internal fun AmountBlock( modifier = Modifier .clip(TangemTheme.shapes.roundedCornersXMedium) .background(backgroundColor) - .clickable(enabled = !isSuccess && !isEditingDisabled, onClick = onClick) + .clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick) .padding( vertical = TangemTheme.dimens.spacing14, horizontal = TangemTheme.dimens.spacing16, @@ -92,7 +92,7 @@ private fun AmountBlockPreview(@PreviewParameter(AmountBlockPreviewProvider::cla TangemThemePreview { AmountBlock( amountState = value, - isSuccess = false, + isClickDisabled = false, isEditingDisabled = false, onClick = {}, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt index 9e762ae382..68dc4c0c8b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt @@ -28,13 +28,13 @@ import com.tangem.features.send.impl.presentation.utils.getCryptoReference import com.tangem.features.send.impl.presentation.utils.getFiatReference @Composable -internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick: () -> Unit) { +internal fun FeeBlock(feeState: SendStates.FeeState, isClickDisabled: Boolean, onClick: () -> Unit) { Column( modifier = Modifier .fillMaxWidth() .clip(TangemTheme.shapes.roundedCornersXMedium) .background(TangemTheme.colors.background.action) - .clickable(enabled = !isSuccess, onClick = onClick) + .clickable(enabled = !isClickDisabled, onClick = onClick) .padding(TangemTheme.dimens.spacing12), ) { Text( @@ -118,7 +118,7 @@ private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) va TangemThemePreview { FeeBlock( feeState = value, - isSuccess = true, + isClickDisabled = true, onClick = {}, ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt index 0b4752d640..4839b83690 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt @@ -25,7 +25,7 @@ import com.tangem.features.send.impl.presentation.state.previewdata.RecipientSta @Composable internal fun RecipientBlock( recipientState: SendStates.RecipientState, - isSuccess: Boolean, + isClickDisabled: Boolean, isEditingDisabled: Boolean, onClick: () -> Unit, ) { @@ -40,7 +40,7 @@ internal fun RecipientBlock( .fillMaxWidth() .clip(TangemTheme.shapes.roundedCornersXMedium) .background(backgroundColor) - .clickable(enabled = !isSuccess && !isEditingDisabled, onClick = onClick) + .clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick) .padding(TangemTheme.dimens.spacing12), ) { AddressBlock(recipientState.addressTextField) @@ -107,7 +107,7 @@ private fun RecipientBlockPreview( TangemThemePreview { RecipientBlock( recipientState = value, - isSuccess = true, + isClickDisabled = true, isEditingDisabled = false, onClick = {}, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt index 6a6cc0550d..6c6de6e979 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt @@ -35,12 +35,13 @@ private const val TAP_HELP_ANIMATION_DELAY = 500L @Composable internal fun SendContent(uiState: SendUiState) { val sendState = uiState.sendState ?: return + val isClickDisabled = sendState.isSending || sendState.isSuccess LazyColumn( modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), ) { blocks(uiState) tapHelp(isDisplay = sendState.showTapHelp) - notifications(sendState.notifications) + notifications(notifications = sendState.notifications, isClickDisabled = isClickDisabled) } } @@ -50,6 +51,7 @@ private fun LazyListScope.blocks(uiState: SendUiState) { val feeState = uiState.feeState ?: return val sendState = uiState.sendState ?: return val isSuccess = sendState.isSuccess + val isClickDisabled = sendState.isSending || isSuccess val timestamp = sendState.transactionDate item(key = BLOCKS_KEY) { @@ -65,19 +67,19 @@ private fun LazyListScope.blocks(uiState: SendUiState) { } RecipientBlock( recipientState = recipientState, - isSuccess = isSuccess, + isClickDisabled = isClickDisabled, isEditingDisabled = uiState.isEditingDisabled, onClick = uiState.clickIntents::showRecipient, ) AmountBlock( amountState = amountState, - isSuccess = isSuccess, + isClickDisabled = isClickDisabled, isEditingDisabled = uiState.isEditingDisabled, onClick = uiState.clickIntents::showAmount, ) FeeBlock( feeState = feeState, - isSuccess = isSuccess, + isClickDisabled = isClickDisabled, onClick = uiState.clickIntents::showFee, ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt index 73ef044814..f2be7e6ed2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt @@ -11,6 +11,7 @@ import java.math.BigDecimal import java.math.RoundingMode private const val FIAT_DECIMALS = 2 +private const val CRYPTO_FEE_DECIMALS = 6 private const val FEE_MINIMUM_VALUE = 0.01 internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): TextReference? { @@ -21,7 +22,7 @@ internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): Tex BigDecimalFormatter.formatCryptoAmount( cryptoAmount = amount.value, cryptoCurrency = amount.currencySymbol, - decimals = amount.decimals, + decimals = amount.decimals.coerceAtMost(CRYPTO_FEE_DECIMALS), ), ), ) @@ -36,24 +37,27 @@ internal fun getFiatReference(value: BigDecimal?, rate: BigDecimal?, appCurrency internal fun getFiatString(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): String { if (value == null || rate == null) return EMPTY_BALANCE_SIGN val feeValue = value.multiply(rate) - val scaled = feeValue.setScale(FIAT_DECIMALS, RoundingMode.UP) ?: BigDecimal.ZERO - val formattedValue = if (scaled < BigDecimal(FEE_MINIMUM_VALUE)) { + return getFiatFormatted(feeValue, appCurrency.code, appCurrency.symbol) +} + +internal fun getFiatFormatted(value: BigDecimal?, currencyCode: String, currencySymbol: String): String { + val scaled = value?.setScale(FIAT_DECIMALS, RoundingMode.UP) ?: BigDecimal.ZERO + return if (scaled < BigDecimal(FEE_MINIMUM_VALUE)) { buildString { append(BigDecimalFormatter.CAN_BE_LOWER_SIGN) append( BigDecimalFormatter.formatFiatAmount( fiatAmount = BigDecimal(FEE_MINIMUM_VALUE), - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, + fiatCurrencyCode = currencyCode, + fiatCurrencySymbol = currencySymbol, ), ) } } else { BigDecimalFormatter.formatFiatAmount( - fiatAmount = feeValue, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, + fiatAmount = value, + fiatCurrencyCode = currencyCode, + fiatCurrencySymbol = currencySymbol, ) } - return formattedValue } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index c90a473a60..d2231aaf7f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -26,7 +26,6 @@ import com.tangem.domain.tokens.* import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.transaction.error.GetFeeError @@ -56,8 +55,9 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.* +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import timber.log.Timber import java.math.BigDecimal import java.util.Locale @@ -76,8 +76,8 @@ internal class SendViewModel @Inject constructor( private val getWalletsUseCase: GetWalletsUseCase, private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, - private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase, - private val getCryptoCurrencyStatusesSyncUseCase: GetCryptoCurrencyStatusesSyncUseCase, + private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, + private val getNetworkAddressesUseCase: GetNetworkAddressesUseCase, private val getFixedTxHistoryItemsUseCase: GetFixedTxHistoryItemsUseCase, private val getFeeUseCase: GetFeeUseCase, private val sendTransactionUseCase: SendTransactionUseCase, @@ -92,6 +92,7 @@ internal class SendViewModel @Inject constructor( private val neverShowTapHelpUseCase: NeverShowTapHelpUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val listenToQrScanningUseCase: ListenToQrScanningUseCase, + private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, validateTransactionUseCase: ValidateTransactionUseCase, currencyChecksRepository: CurrencyChecksRepository, isFeeApproximateUseCase: IsFeeApproximateUseCase, @@ -179,6 +180,7 @@ internal class SendViewModel @Inject constructor( stateRouterProvider = Provider { stateRouter }, currentStateProvider = Provider { uiState }, analyticsEventHandler = analyticsEventHandler, + cryptoCurrencyProvider = Provider { cryptoCurrency }, ) } @@ -187,6 +189,7 @@ internal class SendViewModel @Inject constructor( private set private var userWallet: UserWallet by Delegates.notNull() + private var userWallets: List = emptyList() private var isAmountSubtractAvailable: Boolean = false private var isTapHelpPreviewEnabled: Boolean = false private var coinCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() @@ -204,7 +207,6 @@ internal class SendViewModel @Inject constructor( private var sendIdleTimer = 0L init { - subscribeOnQRScannerResult() subscribeOnCurrencyStatusUpdates() subscribeOnBalanceHidden() getTapHelpPreviewAvailability() @@ -371,7 +373,7 @@ internal class SendViewModel @Inject constructor( cryptoCurrencyStatus = currencyStatus coinCryptoCurrencyStatus = coinCurrencyStatus feeCryptoCurrencyStatus = feeCurrencyStatus - + subscribeOnQRScannerResult() when { uiState.sendState?.isSuccess == true -> { stateRouter.showSend() @@ -399,57 +401,48 @@ internal class SendViewModel @Inject constructor( } private fun getUserWallets() { - getWalletsUseCase() - .conflate() - .distinctUntilChanged() - .onEach { userWallets -> - coroutineScope { - runCatching { - userWallets - .filterNot { it.walletId == userWalletId || it.isLocked } - .map { wallet -> - async(dispatchers.io) { wallet.toAvailableWallet() } - }.awaitAll() - }.onSuccess { result -> - uiState = stateFactory.onLoadedWalletsList(wallets = result) - }.onFailure { - uiState = stateFactory.onLoadedWalletsList(wallets = emptyList()) + viewModelScope.launch(dispatchers.main) { + runCatching { + getWalletsUseCase.invokeSync() + ?.toAvailableWallets() + .orEmpty() + }.onSuccess { result -> + combine(*result.toTypedArray()) { it } + .onEach { + userWallets = it.filterNotNull().toList() + uiState = stateFactory.onLoadedWalletsList(wallets = userWallets) } - } - } - .flowOn(dispatchers.main) - .launchIn(viewModelScope) - } - - private suspend fun UserWallet.toAvailableWallet(): AvailableWallet? { - return if (!isMultiCurrency) { - val status = getCryptoCurrencyStatusSyncUseCase(walletId).getOrNull() - val address = status?.value?.networkAddress.takeIf { - status?.currency?.network?.id == cryptoCurrency.network.id && - status.currency.network.derivationPath !is Network.DerivationPath.Custom - } - address?.let { - AvailableWallet( - name = name, - address = it.defaultAddress.value, - ) - } - } else { - val statuses = getCryptoCurrencyStatusesSyncUseCase(walletId).getOrNull() - val walletCurrency = statuses?.firstOrNull { - it.currency.network.id == cryptoCurrency.network.id && - it.currency.network.derivationPath !is Network.DerivationPath.Custom - } - val address = walletCurrency?.value?.networkAddress - address?.let { - AvailableWallet( - name = name, - address = it.defaultAddress.value, - ) + .flowOn(dispatchers.main) + .launchIn(viewModelScope) + }.onFailure { + uiState = stateFactory.onLoadedWalletsList(wallets = emptyList()) } } } + private suspend fun List.toAvailableWallets(): List> = + filterNot { it.walletId == userWalletId || it.isLocked } + .mapNotNull { wallet -> + val status = if (!wallet.isMultiCurrency) { + getCryptoCurrencyUseCase(wallet.walletId).getOrNull()?.let { + if (it.network.id == cryptoCurrency.network.id) { + getNetworkAddressesUseCase(wallet.walletId, it.network) + } else { + null + } + } + } else { + getNetworkAddressesUseCase(wallet.walletId, cryptoCurrency.network) + } + status?.map { address -> + AvailableWallet( + name = wallet.name, + address = address, + userWalletId = wallet.walletId, + ) + } + } + private suspend fun getTxHistory() { val txHistoryList = getFixedTxHistoryItemsUseCase.getSync( userWalletId = userWalletId, @@ -636,7 +629,7 @@ internal class SendViewModel @Inject constructor( }.saveIn(memoValidationJobHolder) } - private suspend fun validateAddress(value: String): Boolean { + private suspend fun validateAddress(value: String): Boolean = runCatching { val isValidAddress = validateWalletAddressUseCase( userWalletId = userWalletId, network = cryptoCurrency.network, @@ -646,7 +639,7 @@ internal class SendViewModel @Inject constructor( ?.any { it.value == value } ?: true onEnteredValidAddress(isValidAddress, isAddressInWallet) return isValidAddress - } + }.getOrElse { false } private suspend fun checkIfXrpAddressValue(value: String): Boolean { return BlockchainUtils.decodeRippleXAddress(value, cryptoCurrency.network.id.value)?.let { decodedAddress -> @@ -874,11 +867,25 @@ internal class SendViewModel @Inject constructor( uiState = stateFactory.getSendingStateUpdate(isSending = false) updateTransactionStatus(txData) scheduleBalanceUpdate() - analyticsEventHandler.send(SendAnalyticEvents.TransactionScreenOpened) + addTokenToWalletIfNeeded() + sendScreenAnalyticSender.sendTransaction() }, ) } + private fun addTokenToWalletIfNeeded() { + if (cryptoCurrency !is CryptoCurrency.Token) return + + val recipientState = uiState.getRecipientState(stateRouter.isEditState) ?: return + val destinationAddress = recipientState.addressTextField.value + + val maybeUserWallet = userWallets.firstOrNull { it.address == destinationAddress } ?: return + + viewModelScope.launch(dispatchers.io) { + addCryptoCurrenciesUseCase(userWalletId = maybeUserWallet.userWalletId, currency = cryptoCurrency) + } + } + private suspend fun updateTransactionStatus(txData: TransactionData) { val txUrl = getExplorerTransactionUrlUseCase( userWalletId = userWalletId, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 4e12e4c227..55ede01473 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -216,6 +216,7 @@ internal class StateBuilder( val warnings = getWarningsForSuccessState( quoteModel = quoteModel, fromToken = fromToken, + selectedFeeType = selectedFeeType, ) val feeState = createFeeState(quoteModel.txFee, selectedFeeType) val fromCurrencyStatus = quoteModel.fromTokenInfo.cryptoCurrencyStatus @@ -314,12 +315,13 @@ internal class StateBuilder( private fun getWarningsForSuccessState( quoteModel: SwapState.QuotesLoadedState, fromToken: CryptoCurrency, + selectedFeeType: FeeType, ): List { val warnings = mutableListOf() maybeAddDomainWarnings(quoteModel, warnings) maybeAddNeedReserveToCreateAccountWarning(quoteModel, warnings) maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken) - maybeAddNetworkFeeCoverageWarning(quoteModel, warnings) + maybeAddNetworkFeeCoverageWarning(quoteModel, warnings, selectedFeeType) maybeAddUnableCoverFeeWarning(quoteModel, fromToken, warnings) maybeAddInsufficientFundsWarning(quoteModel, warnings) maybeAddTransactionInProgressWarning(quoteModel, warnings) @@ -460,18 +462,37 @@ internal class StateBuilder( private fun maybeAddNetworkFeeCoverageWarning( quoteModel: SwapState.QuotesLoadedState, warnings: MutableList, + selectedFeeType: FeeType, ) { when (quoteModel.preparedSwapConfigState.includeFeeInAmount) { - is IncludeFeeInAmount.Included -> + is IncludeFeeInAmount.Included -> { + val fee = selectFeeByType(selectedFeeType, quoteModel.txFee) ?: return warnings.add( SwapWarning.GeneralWarning( - createNetworkFeeCoverageNotificationConfig(), + createNetworkFeeCoverageNotificationConfig( + quoteModel.fromTokenInfo.tokenAmount.getFormattedCryptoAmount( + quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency, + ), + fee.feeFiatFormatted, + ), ), ) + } else -> Unit } } + private fun selectFeeByType(feeType: FeeType, txFeeState: TxFeeState): TxFee? { + return when (txFeeState) { + TxFeeState.Empty -> null + is TxFeeState.SingleFeeState -> txFeeState.fee + is TxFeeState.MultipleFeeState -> when (feeType) { + FeeType.NORMAL -> txFeeState.normalFee + FeeType.PRIORITY -> txFeeState.priorityFee + } + } + } + private fun maybeAddUnableCoverFeeWarning( quoteModel: SwapState.QuotesLoadedState, fromToken: CryptoCurrency, @@ -547,12 +568,12 @@ internal class StateBuilder( if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder val warnings = mutableListOf() warnings.add(getWarningForError(dataError, fromToken.cryptoCurrencyStatus.currency)) - if (includeFeeInAmount is IncludeFeeInAmount.Included) { - warnings.add( - SwapWarning.GeneralWarning( - createNetworkFeeCoverageNotificationConfig(), - ), + if (includeFeeInAmount is IncludeFeeInAmount.Included && uiStateHolder.fee is FeeItemState.Content) { + val feeCoverageNotification = createNetworkFeeCoverageNotificationConfig( + fromToken.tokenAmount.getFormattedCryptoAmount(fromToken.cryptoCurrencyStatus.currency), + uiStateHolder.fee.amountFiatFormatted, ) + warnings.add(SwapWarning.GeneralWarning(feeCoverageNotification)) } val providerState = getProviderStateForError( swapProvider = swapProvider, @@ -1402,10 +1423,16 @@ internal class StateBuilder( ) } - private fun createNetworkFeeCoverageNotificationConfig(): NotificationConfig { + private fun createNetworkFeeCoverageNotificationConfig( + cryptoAmount: String, + fiatAmount: String, + ): NotificationConfig { return NotificationConfig( title = resourceReference(R.string.send_network_fee_warning_title), - subtitle = resourceReference(R.string.swapping_network_fee_warning_content), + subtitle = resourceReference( + R.string.common_network_fee_warning_content, + wrappedList(cryptoAmount, fiatAmount), + ), iconResId = R.drawable.img_attention_20, ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt index 9236e01d5e..b458c3f622 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt @@ -41,6 +41,7 @@ internal class TokenDetailsNotificationsAnalyticsSender( is TokenDetailsNotification.TopUpWithoutReserve, is TokenDetailsNotification.RentInfo, is TokenDetailsNotification.SwapPromo, + is TokenDetailsNotification.NetworkShutdown, -> null } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index bed8a2a0e2..853191b4a2 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -155,7 +155,11 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { ), ) - class NetworksNoAccount(val network: String, val symbol: String, val amount: String) : Informational( + data class NetworksNoAccount( + private val network: String, + private val symbol: String, + private val amount: String, + ) : Informational( title = resourceReference(R.string.warning_no_account_title), subtitle = resourceReference( id = R.string.no_account_generic, @@ -167,4 +171,9 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { title = resourceReference(id = R.string.warning_no_account_title), subtitle = resourceReference(id = R.string.no_account_send_to_create), ) + + data class NetworkShutdown(private val title: TextReference, private val subtitle: TextReference) : Warning( + title = title, + subtitle = subtitle, + ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index 01535ea1b4..8f368bb1b3 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -3,12 +3,14 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification.* import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.features.tokendetails.impl.R import com.tangem.utils.converter.Converter import com.tangem.utils.extensions.removeBy import kotlinx.collections.immutable.ImmutableList @@ -80,6 +82,10 @@ internal class TokenDetailsNotificationConverter( onSwapClick = clickIntents::onSwapPromoClick, onCloseClick = clickIntents::onSwapPromoDismiss, ) + is CryptoCurrencyWarning.BeaconChainShutdown -> NetworkShutdown( + title = resourceReference(R.string.warning_beacon_chain_retirement_title), + subtitle = resourceReference(R.string.warning_beacon_chain_retirement_content), + ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index de6ddf8b1b..f39a38511b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -121,4 +121,38 @@ sealed class WalletScreenAnalyticsEvent { data object DeleteWalletTapped : MainScreen(event = "Button - Delete Wallet Tapped") } + + sealed class Promotion( + event: String, + params: Map = mapOf(), + ) : AnalyticsEvent(category = "Promotion", event = event, params = params) { + class NoticePromotionBanner( + source: AnalyticsParam.ScreensSources, + programName: String, + ) : Promotion( + event = "Notice - Promotion Banner", + params = mapOf( + AnalyticsParam.SOURCE to source.value, + "Program Name" to programName, + ), + ) + + class PromotionBannerClicked( + source: AnalyticsParam.ScreensSources, + programName: String, + action: BannerAction, + ) : Promotion( + event = "Promo Banner Clicked", + params = mapOf( + AnalyticsParam.SOURCE to source.value, + "Program Name" to programName, + "Action" to action.action, + ), + ) { + sealed class BannerAction(val action: String) { + data object Clicked : BannerAction(action = "Clicked") + data object Closed : BannerAction(action = "Closed") + } + } + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt index ae05161912..59d4331387 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt @@ -2,6 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.analytics.utils import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState @@ -44,6 +46,10 @@ internal class WalletWarningsAnalyticsSender @Inject constructor( is WalletNotification.Informational.MissingAddresses -> MainScreen.MissingAddresses is WalletNotification.RateApp -> MainScreen.HowDoYouLikeTangem is WalletNotification.Critical.BackupError -> MainScreen.BackupError + is WalletNotification.TravalaPromo -> WalletScreenAnalyticsEvent.Promotion.NoticePromotionBanner( + source = AnalyticsParam.ScreensSources.Main, + programName = "Travala", + ) is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender] is WalletNotification.Informational.NoAccount, is WalletNotification.Warning.LowSignatures, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 51fbfe6742..a2adf6b292 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -6,7 +6,7 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.promo.PromoBanner import com.tangem.domain.settings.IsReadyToShowRateAppUseCase -import com.tangem.domain.settings.ShouldShowSwapPromoWalletUseCase +import com.tangem.domain.settings.ShouldShowTravalaPromoWalletUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency @@ -34,7 +34,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private val getTokenListUseCase: GetTokenListUseCase, private val isDemoCardUseCase: IsDemoCardUseCase, private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, - private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase, + private val shouldShowTravalaPromoWalletUseCase: ShouldShowTravalaPromoWalletUseCase, private val promoRepository: PromoRepository, private val isNeedToBackupUseCase: IsNeedToBackupUseCase, private val backupValidator: BackupValidator, @@ -45,18 +45,18 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow> { val cardTypesResolver = userWallet.scanResponse.cardTypesResolver - val promoFlow = flow { emit(promoRepository.getChangellyPromoBanner()) } + val travalaPromoFlow = flow { emit(promoRepository.getTravalaPromoBanner()) } return combine( flow = getTokenListUseCase.launch(userWallet.walletId).conflate(), flow2 = isReadyToShowRateAppUseCase().conflate(), flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(), - flow4 = shouldShowSwapPromoWalletUseCase().conflate(), - flow5 = promoFlow, - ) { maybeTokenList, isReadyToShowRating, isNeedToBackup, shouldShowPromo, promoBanner -> + flow4 = shouldShowTravalaPromoWalletUseCase().conflate(), + flow5 = travalaPromoFlow.conflate(), + ) { maybeTokenList, isReadyToShowRating, isNeedToBackup, shouldShowTravalaPromo, promoBanner -> readyForRateAppNotification = true buildList { - addSwapPromoNotification(shouldShowPromo, promoBanner, clickIntents) + addTravalaPromoNotification(shouldShowTravalaPromo, promoBanner, clickIntents) addCriticalNotifications(userWallet) @@ -69,16 +69,18 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( } } - private fun MutableList.addSwapPromoNotification( + private fun MutableList.addTravalaPromoNotification( shouldShowPromo: Boolean, promoBanner: PromoBanner?, clickIntents: WalletClickIntents, ) { promoBanner ?: return - val promoNotification = WalletNotification.SwapPromo( + val promoNotification = WalletNotification.TravalaPromo( startDateTime = promoBanner.bannerState.timeline.start, endDateTime = promoBanner.bannerState.timeline.end, - onCloseClick = clickIntents::onCloseSwapPromoClick, + bannerLink = promoBanner.bannerState.link, + onBookNowButtonClick = clickIntents::onTravalaPromoClick, + onCloseClick = clickIntents::onCloseTravalaPromoClick, ) addIf( element = promoNotification, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletImageResolver.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletImageResolver.kt index 7c7adbde20..3613a6439a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletImageResolver.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletImageResolver.kt @@ -34,6 +34,9 @@ internal object WalletImageResolver { cardTypesResolver.isJrWallet() -> userWallet.resolveJrWallet() cardTypesResolver.isGrimWallet() -> userWallet.resolveGrimWallet() cardTypesResolver.isSatoshiFriendsWallet() -> userWallet.resolveSatoshiWallet() + cardTypesResolver.isBitcoinPizzaDayWallet() -> userWallet.resolveBitcoinPizzaDayWallet() + cardTypesResolver.isVeChainWallet() -> userWallet.resolveVeChainWallet() + cardTypesResolver.isNewWorldEliteWallet() -> userWallet.resolveNewWorldEliteWallet() cardTypesResolver.isWallet2() -> userWallet.resolveWallet2() cardTypesResolver.isShibaWallet() -> userWallet.resolveShibaWallet() cardTypesResolver.isTangemWallet() -> userWallet.resolveWallet1() @@ -130,6 +133,27 @@ internal object WalletImageResolver { ) } + private fun UserWallet.resolveBitcoinPizzaDayWallet(): Int? { + return resolveWallet2( + oneBackupResId = R.drawable.ill_pizza_day_card2_120_106, + twoBackupResId = R.drawable.ill_pizza_day_card3_120_106, + ) + } + + private fun UserWallet.resolveVeChainWallet(): Int? { + return resolveWallet2( + oneBackupResId = R.drawable.ill_vechain_card2_120_106, + twoBackupResId = R.drawable.ill_vechain_card3_120_106, + ) + } + + private fun UserWallet.resolveNewWorldEliteWallet(): Int? { + return resolveWallet2( + oneBackupResId = R.drawable.ill_nwe_card2_120_106, + twoBackupResId = R.drawable.ill_nwe_card3_120_106, + ) + } + private fun UserWallet.resolveWallet1(): Int? { return resolveWalletWithBackups { count -> when (count) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt index 9f7f5ea139..c34e986c29 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt @@ -2,10 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.pluralReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.feature.wallet.impl.R import org.joda.time.DateTime @@ -173,6 +171,34 @@ sealed class WalletNotification(val config: NotificationConfig) { ), ) + data class TravalaPromo( + val startDateTime: DateTime, + val endDateTime: DateTime, + val bannerLink: String?, + val onBookNowButtonClick: (String?) -> Unit, + val onCloseClick: () -> Unit, + ) : WalletNotification( + config = NotificationConfig( + title = resourceReference(id = R.string.main_travala_promotion_title), + subtitle = resourceReference( + id = R.string.main_travala_promotion_description, + wrappedList( + DateTimeFormatters.formatDate(startDateTime, DateTimeFormatters.dateMMMMd), + DateTimeFormatters.formatDate(endDateTime, DateTimeFormatters.dateMMMMd), + ), + ), + // Stub. Travala has its own Composable implementation with correct img + iconResId = R.drawable.ic_star_24, + // Stub. Travala has its own Composable implementation with correct img + backgroundResId = R.drawable.ic_star_24, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + onClick = { onBookNowButtonClick(bannerLink) }, + text = resourceReference(R.string.main_travala_promotion_button), + ), + onCloseClick = onCloseClick, + ), + ) + data class SwapPromo( val startDateTime: DateTime, val endDateTime: DateTime, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt index 476f3701e4..6b783e0eb0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.ui.Modifier import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.notifications.NotificationWithBackground +import com.tangem.core.ui.components.notifications.TravalaNotificationWithBackground import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import kotlinx.collections.immutable.ImmutableList @@ -25,23 +26,33 @@ internal fun LazyListScope.notifications(configs: ImmutableList TangemTheme.colors.icon.warning - is WalletNotification.Informational -> TangemTheme.colors.icon.accent - is WalletNotification.RateApp -> TangemTheme.colors.icon.attention - is WalletNotification.UnlockWallets -> TangemTheme.colors.icon.primary1 - else -> null - }, - ) + // TODO develop promo banner general component + when (it) { + is WalletNotification.SwapPromo -> { + NotificationWithBackground( + config = it.config, + modifier = modifier.animateItemPlacement(), + ) + } + is WalletNotification.TravalaPromo -> { + TravalaNotificationWithBackground( + config = it.config, + modifier = modifier.animateItemPlacement(), + ) + } + else -> { + Notification( + config = it.config, + modifier = modifier.animateItemPlacement(), + iconTint = when (it) { + is WalletNotification.Critical -> TangemTheme.colors.icon.warning + is WalletNotification.Informational -> TangemTheme.colors.icon.accent + is WalletNotification.RateApp -> TangemTheme.colors.icon.attention + is WalletNotification.UnlockWallets -> TangemTheme.colors.icon.primary1 + else -> null + }, + ) + } } }, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt index 7e47a2a490..10d52a0047 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt @@ -1,8 +1,12 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.domain.card.DeleteSavedAccessCodesUseCase +import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.DeleteWalletUseCase +import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.UpdateWalletUseCase import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader @@ -32,9 +36,13 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, private val walletEventSender: WalletEventSender, private val walletScreenContentLoader: WalletScreenContentLoader, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val updateWalletUseCase: UpdateWalletUseCase, private val deleteWalletUseCase: DeleteWalletUseCase, + private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase, private val analyticsEventHandler: AnalyticsEventHandler, + private val reduxStateHolder: ReduxStateHolder, private val dispatchers: CoroutineDispatcherProvider, ) : BaseWalletClickIntents(), WalletCardClickIntents { @@ -72,7 +80,18 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( override fun onDeleteAfterConfirmationClick(userWalletId: UserWalletId) { viewModelScope.launch(dispatchers.main) { walletScreenContentLoader.cancel(userWalletId) + + val deletedUserWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: return@launch + + deleteSavedAccessCodesUseCase(cardId = deletedUserWallet.cardId) + .onLeft { Timber.e(it.toString()) } + deleteWalletUseCase(userWalletId) + .onRight { + getSelectedWalletSyncUseCase().getOrNull()?.let { + reduxStateHolder.onUserWalletSelected(it) + } + } .onLeft { Timber.e(it.toString()) } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index 553dafc78b..418a4d8afd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -11,6 +11,7 @@ import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.NeverToSuggestRateAppUseCase import com.tangem.domain.settings.RemindToRateAppLaterUseCase import com.tangem.domain.settings.ShouldShowSwapPromoWalletUseCase +import com.tangem.domain.settings.ShouldShowTravalaPromoWalletUseCase import com.tangem.domain.tokens.FetchTokenListUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType @@ -19,6 +20,7 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.UnlockWalletsUseCase import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletClickHandler @@ -56,6 +58,10 @@ internal interface WalletWarningsClickIntents { fun onCloseRateAppWarningClick() fun onCloseSwapPromoClick() + + fun onTravalaPromoClick(link: String?) + + fun onCloseTravalaPromoClick() } @Suppress("LongParameterList") @@ -75,6 +81,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( private val reduxStateHolder: ReduxStateHolder, private val dispatchers: CoroutineDispatcherProvider, private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase, + private val shouldShowTravalaPromoWalletUseCase: ShouldShowTravalaPromoWalletUseCase, ) : BaseWalletClickIntents(), WalletWarningsClickIntents { override fun onAddBackupCardClick() { @@ -212,6 +219,34 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( } } + override fun onTravalaPromoClick(link: String?) { + analyticsEventHandler.send( + WalletScreenAnalyticsEvent.Promotion.PromotionBannerClicked( + source = AnalyticsParam.ScreensSources.Main, + programName = "Travala", + action = WalletScreenAnalyticsEvent.Promotion.PromotionBannerClicked.BannerAction.Clicked, + ), + ) + link?.let { + viewModelScope.launch(dispatchers.main) { + router.openUrl(link) + } + } + } + + override fun onCloseTravalaPromoClick() { + analyticsEventHandler.send( + WalletScreenAnalyticsEvent.Promotion.PromotionBannerClicked( + source = AnalyticsParam.ScreensSources.Main, + programName = "Travala", + action = WalletScreenAnalyticsEvent.Promotion.PromotionBannerClicked.BannerAction.Closed, + ), + ) + viewModelScope.launch(dispatchers.main) { + shouldShowTravalaPromoWalletUseCase.neverToShow() + } + } + private suspend fun getSelectedUserWallet(): UserWallet? { val userWalletId = stateHolder.getSelectedWalletId() return getUserWalletUseCase(userWalletId).getOrElse { diff --git a/features/wallet/impl/src/main/res/drawable/ill_nwe_card2_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_nwe_card2_120_106.webp new file mode 100644 index 0000000000..b54656293e Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_nwe_card2_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_nwe_card3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_nwe_card3_120_106.webp new file mode 100644 index 0000000000..feecda86b3 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_nwe_card3_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_pizza_day_card2_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_pizza_day_card2_120_106.webp new file mode 100644 index 0000000000..2608db4c6e Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_pizza_day_card2_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_pizza_day_card3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_pizza_day_card3_120_106.webp new file mode 100644 index 0000000000..d47e557121 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_pizza_day_card3_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_vechain_card2_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_vechain_card2_120_106.webp new file mode 100644 index 0000000000..bb5f23d301 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_vechain_card2_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_vechain_card3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_vechain_card3_120_106.webp new file mode 100644 index 0000000000..249477b20f Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_vechain_card3_120_106.webp differ diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 67d4201243..7c24809756 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -83,10 +83,11 @@ web3j = "4.10.1" leakcanary = "2.13" decompose = "2.2.2" room = "2.6.1" +markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-628" +tangemBlockchainSdk = "develop-633" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-354" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ @@ -258,4 +259,5 @@ decompose-ext-compose = { module = "com.arkivanov.decompose:extensions-compose-j room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } +markdown = { module = "org.jetbrains:markdown", version.ref = "markdown" } # endregion Other diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index 4571c8a31e..35b8386cd9 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -49,4 +49,10 @@ object BlockchainUtils { val blockchain = Blockchain.fromId(networkId) return blockchain == Blockchain.Cardano } + + /** If current [networkId] is BeaconChain */ + fun isBeaconChain(networkId: String): Boolean { + val blockchain = Blockchain.fromId(networkId) + return blockchain == Blockchain.Binance || blockchain == Blockchain.BinanceTestnet + } } \ No newline at end of file