Updated on 2026-08-14
This commit is contained in:
commit
54216a69e5
59 changed files with 414 additions and 246 deletions
|
|
@ -18,10 +18,14 @@ class AmplitudeAnalyticsHandler(
|
|||
}
|
||||
|
||||
class Builder : AnalyticsHandlerBuilder {
|
||||
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when {
|
||||
!data.isDebug -> AmplitudeClient(data.application, data.config.amplitudeApiKey)
|
||||
data.isDebug && data.logConfig.amplitude -> AmplitudeLogClient(data.jsonConverter)
|
||||
else -> null
|
||||
}?.let { AmplitudeAnalyticsHandler(it) }
|
||||
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler {
|
||||
return AmplitudeAnalyticsHandler(
|
||||
client = if (data.logConfig.amplitude) {
|
||||
AmplitudeLogClient(data.jsonConverter)
|
||||
} else {
|
||||
AmplitudeClient(data.application, data.config.amplitudeApiKey)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -239,9 +239,8 @@ class WalletConnectSdkHelper {
|
|||
private suspend fun signTransaction(data: WcTransactionData, cardId: String?): String? {
|
||||
val dataToSign = EthereumUtils.buildTransactionToSign(
|
||||
transactionData = data.transaction,
|
||||
nonce = null,
|
||||
blockchain = data.walletManager.wallet.blockchain,
|
||||
) ?: return null
|
||||
)
|
||||
|
||||
val command = SignHashCommand(
|
||||
hash = dataToSign.hash,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ internal fun StoriesButton(
|
|||
showProgress = showProgress,
|
||||
enabled = true,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
iconPadding = when (icon) {
|
||||
is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing4
|
||||
is TangemButtonIconPosition.End,
|
||||
|
|
|
|||
|
|
@ -135,4 +135,13 @@ interface TangemTechApi {
|
|||
@ReadTimeout(duration = 5, unit = TimeUnit.SECONDS)
|
||||
@GET("networks/providers")
|
||||
suspend fun getBlockchainProviders(): Map<String, List<ProviderModel>>
|
||||
|
||||
companion object {
|
||||
val marketsQuoteFields = listOf(
|
||||
"price",
|
||||
"priceChange24h",
|
||||
"priceChange1w",
|
||||
"priceChange30d",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@MustBeDocumented
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class DevTangemApi
|
||||
|
|
@ -35,7 +35,6 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
internal object NetworkModule {
|
||||
|
||||
private const val DEV_V1_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/"
|
||||
private const val PROD_V2_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v2/"
|
||||
private const val TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS = 60L
|
||||
|
||||
|
|
@ -128,44 +127,27 @@ internal object NetworkModule {
|
|||
}
|
||||
|
||||
@Provides
|
||||
@DevTangemApi
|
||||
@Singleton
|
||||
fun provideTangemTechDevApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
appVersionProvider: AppVersionProvider,
|
||||
): TangemTechApi {
|
||||
return provideTangemTechApiInternal(
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
appVersionProvider = appVersionProvider,
|
||||
baseUrl = DEV_V1_TANGEM_TECH_BASE_URL,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
@Provides
|
||||
@DevTangemApi
|
||||
@Singleton
|
||||
fun provideTangemTechMarketsApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
appVersionProvider: AppVersionProvider,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
): TangemTechMarketsApi {
|
||||
return provideTangemTechApiInternal(
|
||||
return createApi(
|
||||
id = ApiConfig.ID.TangemTech,
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
appVersionProvider = appVersionProvider,
|
||||
baseUrl = DEV_V1_TANGEM_TECH_BASE_URL,
|
||||
timeouts = Timeouts(
|
||||
callTimeoutSeconds = TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS,
|
||||
connectTimeoutSeconds = TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS,
|
||||
readTimeoutSeconds = TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS,
|
||||
),
|
||||
requestHeaders = listOf(AppVersionPlatformHeaders(appVersionProvider)),
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
clientBuilder = {
|
||||
this.callTimeout(TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
.connectTimeout(TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
.readTimeout(TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
.applyTimeoutAnnotations()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Deprecated("use createApi instead")
|
||||
private inline fun <reified T> provideTangemTechApiInternal(
|
||||
moshi: Moshi,
|
||||
context: Context,
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ private class DefaultBatchListSource<TKey, TData, TRequestParams : Any, TUpdate>
|
|||
|
||||
scope.launch {
|
||||
context.actionsFlow
|
||||
.conflate()
|
||||
.buffer()
|
||||
.collect { action ->
|
||||
collectActions(action)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -383,6 +383,7 @@
|
|||
<string name="markets_token_details_blockchain_site">Webseite</string>
|
||||
<string name="markets_token_details_buy_pressure">Kaufdruck</string>
|
||||
<string name="markets_token_details_buy_pressure_description">Die Differenz zwischen Käufer- und Verkäufervolumen</string>
|
||||
<string name="markets_token_details_buy_pressure_full">Kaufdruck</string>
|
||||
<string name="markets_token_details_circulating_supply">Umlaufmenge</string>
|
||||
<string name="markets_token_details_circulating_supply_description">Die Gesamtzahl der Coins, die für den Handel verfügbar sind und auf dem Markt zirkulieren</string>
|
||||
<string name="markets_token_details_experienced_buyers">Erfahrene Käufer</string>
|
||||
|
|
@ -650,6 +651,8 @@
|
|||
<string name="staking_amount_requirement_error">Die Anzahl der zu stakenden Krypros muss mindesten %s betragen</string>
|
||||
<string name="staking_amount_tron_integer_error">Der Stakingbetrag wird aufgrund der Netzwerkregeln auf %1$s TRX aufgerundet.</string>
|
||||
<string name="staking_claim_unstaked">Nicht gestakte beanspruche</string>
|
||||
<string name="staking_details_account_fee">Gebühr für das Staking-Konto</string>
|
||||
<string name="staking_details_account_fee_info">Ein Staking-Konto ist ein spezielles Konto, auf dem eingesetzte SOL-Token gelagert werden. Es wird erstellt, wenn du deine Token an einen Validator delegierst, um an der Transaktionsvalidierung teilzunehmen und Belohnungen zu verdienen. Für die Erstellung des Staking-Kontos wird eine geringe Gebühr erhoben, die nach Abschluss des Stakings zurückgegeben wird.</string>
|
||||
<string name="staking_details_annual_percentage_rate">Jährliche prozentuale Rendite</string>
|
||||
<string name="staking_details_annual_percentage_rate_info">Die jährliche prozentuale Rendite, die du durch die Teilnahme am Staking erzielen kannst.</string>
|
||||
<string name="staking_details_apr">Effektiver Jahreszins</string>
|
||||
|
|
@ -721,6 +724,7 @@
|
|||
<string name="staking_transaction_in_progress_text">Die Transaktion wird bearbeitet! Derzeit findet eine Validierung in der Blockchain statt. Dies kann einige Minuten dauern.</string>
|
||||
<string name="staking_unbonding">Lösen der Bindungen</string>
|
||||
<string name="staking_unlocked_locked">Gelocktes unlocken</string>
|
||||
<string name="staking_unlocking">Entsperren</string>
|
||||
<string name="staking_unstaked">Unstaken</string>
|
||||
<string name="staking_unstaking">Staking beenden</string>
|
||||
<string name="staking_unstaking_item_name">Unstake Assets %s</string>
|
||||
|
|
@ -863,6 +867,8 @@
|
|||
<string name="warning_backup_errors_title">Aktivierungsfehler</string>
|
||||
<string name="warning_beacon_chain_retirement_content">Laut den Entwicklern des BNB-Netzes wird die Unterstützung für den BEP-2-Standard im Juni 2024 enden. Um den Verlust von Vermögenswerten mit diesem Standard zu vermeiden, konvertiere bitte in den BEP-20 Standard. Nutze gerne unseren Swap-Service, um sie auf das BNB Smart Chain Netzwerk zu übertragen.</string>
|
||||
<string name="warning_beacon_chain_retirement_title">BNB Beacon Chain wird abgeschaltet</string>
|
||||
<string name="warning_blocked_funds_for_fee_message">Bitte zahle ein paar %1$s ein, um die Netzwerkgebühr zu decken</string>
|
||||
<string name="warning_blocked_funds_for_fee_title">Unzureichende Mittel zur Deckung der Netzgebühr</string>
|
||||
<string name="warning_button_could_be_better">Ausbaufähig</string>
|
||||
<string name="warning_button_like_it">Gefällt mir</string>
|
||||
<string name="warning_button_ok">OK, habe ich verstanden!</string>
|
||||
|
|
|
|||
|
|
@ -382,6 +382,7 @@
|
|||
<string name="markets_token_details_blockchain_site">Sitio web</string>
|
||||
<string name="markets_token_details_buy_pressure">Presión de compra</string>
|
||||
<string name="markets_token_details_buy_pressure_description">La diferencia entre el volumen de compradores y el volumen de vendedores</string>
|
||||
<string name="markets_token_details_buy_pressure_full">Presión de compra</string>
|
||||
<string name="markets_token_details_circulating_supply">Suministro circulante</string>
|
||||
<string name="markets_token_details_circulating_supply_description">El número total de monedas que están disponibles para el comercio y que circulan en el mercado.</string>
|
||||
<string name="markets_token_details_experienced_buyers">Compradores experimentados</string>
|
||||
|
|
|
|||
|
|
@ -382,6 +382,7 @@
|
|||
<string name="markets_token_details_blockchain_site">Site web</string>
|
||||
<string name="markets_token_details_buy_pressure">Pression d\'achat</string>
|
||||
<string name="markets_token_details_buy_pressure_description">La différence entre le volume des acheteurs et le volume des vendeurs</string>
|
||||
<string name="markets_token_details_buy_pressure_full">Pression d\'achat</string>
|
||||
<string name="markets_token_details_circulating_supply">Approvisionnement en circulation</string>
|
||||
<string name="markets_token_details_circulating_supply_description">Le nombre total de pièces disponibles pour le trading et en circulation sur le marché</string>
|
||||
<string name="markets_token_details_experienced_buyers">Acheteurs expérimentés</string>
|
||||
|
|
|
|||
|
|
@ -353,6 +353,7 @@
|
|||
<string name="markets_loading_error_title">データを読み込めません…</string>
|
||||
<string name="markets_loading_no_data_title">データなし</string>
|
||||
<string name="markets_quick_actions">クイックアクション</string>
|
||||
<string name="markets_search_header_title">マーケットから探す</string>
|
||||
<string name="markets_search_result_title">結果</string>
|
||||
<string name="markets_search_see_tokens_under_100k">時価総額10万ドル以下のトークンを見る</string>
|
||||
<string name="markets_search_show_tokens">トークンを表示</string>
|
||||
|
|
@ -379,6 +380,7 @@
|
|||
<string name="markets_token_details_blockchain_site">ウェブサイト</string>
|
||||
<string name="markets_token_details_buy_pressure">買い圧力</string>
|
||||
<string name="markets_token_details_buy_pressure_description">買い手と売り手の取引量の差</string>
|
||||
<string name="markets_token_details_buy_pressure_full">買い圧力</string>
|
||||
<string name="markets_token_details_circulating_supply">循環供給量</string>
|
||||
<string name="markets_token_details_circulating_supply_description">取引可能で市場に流通しているコインの総数</string>
|
||||
<string name="markets_token_details_experienced_buyers">経験豊富な買い手</string>
|
||||
|
|
@ -640,6 +642,8 @@
|
|||
<string name="staking_amount_requirement_error">ステーキング金額は %s 以上である必要があります</string>
|
||||
<string name="staking_amount_tron_integer_error">ネットワークルールにより、ステーキング金額は%1$s TRX に切り上げられます。</string>
|
||||
<string name="staking_claim_unstaked">ステーキング解除分を請求する</string>
|
||||
<string name="staking_details_account_fee">ステーキングアカウント手数料</string>
|
||||
<string name="staking_details_account_fee_info">ステーキングアカウントは、ステーキングされたSOLトークンが保管される特別なアカウントです。取引の検証に参加して報酬を得るために、トークンをバリデーターに委任すると、このアカウントが作成されます。ステーキングアカウントの作成には少額の手数料がかかりますが、この手数料はステーキングが完了すると返金されます。</string>
|
||||
<string name="staking_details_annual_percentage_rate">年率</string>
|
||||
<string name="staking_details_annual_percentage_rate_info">ステーキングに参加することで得られる年間収益率。</string>
|
||||
<string name="staking_details_apr">APR</string>
|
||||
|
|
@ -711,6 +715,7 @@
|
|||
<string name="staking_transaction_in_progress_text">取引を処理中です。現在、ブロックチェーンで検証が行われています。これには数分かかる場合があります。</string>
|
||||
<string name="staking_unbonding">ステーキング解約中</string>
|
||||
<string name="staking_unlocked_locked">ロック解除</string>
|
||||
<string name="staking_unlocking">ロック解除中</string>
|
||||
<string name="staking_unstaked">ステーキングされていない</string>
|
||||
<string name="staking_unstaking">ステーキング解除</string>
|
||||
<string name="staking_unstaking_item_name">ステーキング解除中の資産%s</string>
|
||||
|
|
@ -853,6 +858,8 @@
|
|||
<string name="warning_backup_errors_title">アクティベーションに失敗しました</string>
|
||||
<string name="warning_beacon_chain_retirement_content">BNBネットワーク開発者によると、BEP-2規格のサポートは2024年6月に終了します。この規格の資産を失わないために、BEP-20規格に変換してください。BNBスマートチェーンネットワークへ移行するには、Tangemのスワップサービスをご利用ください。</string>
|
||||
<string name="warning_beacon_chain_retirement_title">BNBビーコンチェーンは閉鎖されます。</string>
|
||||
<string name="warning_blocked_funds_for_fee_message">ネットワーク手数料をカバーするために%1$sを入金してください</string>
|
||||
<string name="warning_blocked_funds_for_fee_title">ネットワーク手数料をカバーする資金が不足しています</string>
|
||||
<string name="warning_button_could_be_better">もっと良くなるはず</string>
|
||||
<string name="warning_button_like_it">気に入った</string>
|
||||
<string name="warning_button_ok">はい、わかりました!</string>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
<string name="biometric_lockout_warning_title">Слишком много попыток</string>
|
||||
<string name="biometric_unavailable_warning">Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона.</string>
|
||||
<string name="button_start_backup_process">Начать резервное копирование</string>
|
||||
<string name="buy_token_description">С помощью банковской карты или банковского аккаунта</string>
|
||||
<string name="buy_token_description">Используйте банковскую карту или другие методы оплаты</string>
|
||||
<plurals name="card_label_card_count">
|
||||
<item quantity="one">%d карта</item>
|
||||
<item quantity="few">%d карты</item>
|
||||
|
|
@ -276,7 +276,7 @@
|
|||
<string name="express_token_list_empty_search">Токены не найдены. Пожалуйста, попробуйте другой запрос</string>
|
||||
<string name="express_transaction_id">ID: %s</string>
|
||||
<string name="express_transaction_id_copied">ID транзакции скопирован</string>
|
||||
<string name="exсhange_token_description">С другой валютой в вашем кошельке</string>
|
||||
<string name="exсhange_token_description">Конвертируйте одну из ваших валют в другую</string>
|
||||
<string name="feedback_data_collection_message">Информация ниже не является обязательной. Вы можете стереть её, если хотите.</string>
|
||||
<string name="feedback_preface_rate_negative">Расскажите, каких функций вам не хватает, и мы постараемся вам помочь.</string>
|
||||
<string name="feedback_preface_scan_failed">Скажите, пожалуйста, какая у вас карта?</string>
|
||||
|
|
@ -352,10 +352,12 @@
|
|||
<string name="markets_common_my_portfolio">Мой портфель</string>
|
||||
<string name="markets_common_title">Рынок</string>
|
||||
<string name="markets_generate_addresses_notification">Чтобы создать адреса для выбранных сетей, отсканируйте вашу карту Tangem кошелька</string>
|
||||
<string name="markets_hint">Потяните вверх или коснитесь поисковой строки, чтобы добавить токен</string>
|
||||
<string name="markets_insights_info_description_message">Данные раздела получены из следующих сетей: %s</string>
|
||||
<string name="markets_loading_error_title">Невозможно загрузить данные</string>
|
||||
<string name="markets_loading_no_data_title">Нет данных</string>
|
||||
<string name="markets_quick_actions">Быстрые действия</string>
|
||||
<string name="markets_search_header_title">Искать на маркете</string>
|
||||
<string name="markets_search_result_title">Результат</string>
|
||||
<string name="markets_search_see_tokens_under_100k">Токены с капитализацией меньше 100к USD</string>
|
||||
<string name="markets_search_show_tokens">Показать токены</string>
|
||||
|
|
@ -385,6 +387,7 @@
|
|||
<string name="markets_token_details_blockchain_site">Веб-сайт</string>
|
||||
<string name="markets_token_details_buy_pressure">Покуп. предпоч.</string>
|
||||
<string name="markets_token_details_buy_pressure_description">Разница между объемом покупателей и продавцов</string>
|
||||
<string name="markets_token_details_buy_pressure_full">Покуп. предпоч.</string>
|
||||
<string name="markets_token_details_circulating_supply">Циркулир. предл.</string>
|
||||
<string name="markets_token_details_circulating_supply_description">Общее количество монет, доступных для торговли и находящихся в обращении на рынке</string>
|
||||
<string name="markets_token_details_experienced_buyers">Опытные трейдеры</string>
|
||||
|
|
@ -523,7 +526,7 @@
|
|||
<string name="qr_scanner_camera_denied_title">Доступ к камере запрещен</string>
|
||||
<string name="receive_bottom_sheet_warning_message">%1$s (%2$s) в сети %3$s</string>
|
||||
<string name="receive_bottom_sheet_warning_message_full">Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств.</string>
|
||||
<string name="receive_token_description">Покажи QR-код или поделись своим адресом</string>
|
||||
<string name="receive_token_description">Переводите средства с другого кошелька или биржи</string>
|
||||
<string name="referral_button_participate">Участвовать</string>
|
||||
<string name="referral_error_failed_to_load_info">Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="referral_error_failed_to_load_info_with_reason">Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже.</string>
|
||||
|
|
@ -653,7 +656,9 @@
|
|||
<string name="settings_forget_wallet_footer">Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова.</string>
|
||||
<string name="settings_wallet_name_title">Имя</string>
|
||||
<string name="staking_amount_requirement_error">Сумма для стейкинга должна быть не менее %s</string>
|
||||
<string name="staking_amount_tron_integer_error">Согласно правилам сети, сумма стейкинга будет округлена до %1$sTRX.</string>
|
||||
<string name="staking_claim_unstaked">Забрать средства</string>
|
||||
<string name="staking_details_account_fee_info">Стейкинг аккаунт — это специальный счет, на котором хранятся застейканные токены SOL. Он создается при делегировании ваших токенов валидатору для участия в подтверждении транзакций и получении наград. За создание стейкинг аккаунта взимается небольшая комиссия, которая возвращается после завершения стейкинга.</string>
|
||||
<string name="staking_details_annual_percentage_rate">Процентная ставка</string>
|
||||
<string name="staking_details_annual_percentage_rate_info">Годовой процентный доход, который вы можете получить от участия в стейкинге.</string>
|
||||
<string name="staking_details_apr">APR</string>
|
||||
|
|
@ -697,6 +702,7 @@
|
|||
<string name="staking_reward_schedule_block">Блок</string>
|
||||
<string name="staking_reward_schedule_day">День</string>
|
||||
<string name="staking_reward_schedule_each_day">Каждый день</string>
|
||||
<string name="staking_reward_schedule_each_minute">Каждую минуту</string>
|
||||
<string name="staking_reward_schedule_epoch">Эпоха</string>
|
||||
<string name="staking_reward_schedule_era">Эра</string>
|
||||
<string name="staking_reward_schedule_hour">Час</string>
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@
|
|||
<string name="common_go_to_provider">Перейти до провайдера</string>
|
||||
<string name="common_go_to_token">Перейти до токену</string>
|
||||
<string name="common_import">Імпортувати</string>
|
||||
<string name="common_in_progress">процесі</string>
|
||||
<string name="common_in_progress">В процесі</string>
|
||||
<string name="common_later">Пізніше</string>
|
||||
<string name="common_left">Залишилося %1$s</string>
|
||||
<string name="common_locked">Заблокований</string>
|
||||
|
|
@ -357,6 +357,7 @@
|
|||
<string name="markets_common_my_portfolio">Моє портфоліо</string>
|
||||
<string name="markets_common_title">Маркет</string>
|
||||
<string name="markets_generate_addresses_notification">Щоб згенерувати адреси для обраних мереж, потрібно відсканувати свою картку Tangem</string>
|
||||
<string name="markets_hint">Щоб додати токени, потягніть вгору або торкніться панелі пошуку</string>
|
||||
<string name="markets_insights_info_description_message">Дані цього розділу отримані з наступних мереж: %s</string>
|
||||
<string name="markets_loading_error_title">Не вдалося завантажити дані...</string>
|
||||
<string name="markets_loading_no_data_title">Немає даних</string>
|
||||
|
|
@ -367,13 +368,13 @@
|
|||
<string name="markets_search_token_no_result_title">Жодного результату</string>
|
||||
<string name="markets_select_network">Виберіть мережу</string>
|
||||
<string name="markets_select_wallet">Оберіть гаманець</string>
|
||||
<string name="markets_selector_interval_1m_title">1 міс.</string>
|
||||
<string name="markets_selector_interval_1y_title">1 рік</string>
|
||||
<string name="markets_selector_interval_24h_title">24 год.</string>
|
||||
<string name="markets_selector_interval_3m_title">3 міс.</string>
|
||||
<string name="markets_selector_interval_6m_title">6 міс.</string>
|
||||
<string name="markets_selector_interval_1m_title">1м</string>
|
||||
<string name="markets_selector_interval_1y_title">1р</string>
|
||||
<string name="markets_selector_interval_24h_title">24г</string>
|
||||
<string name="markets_selector_interval_3m_title">3м</string>
|
||||
<string name="markets_selector_interval_6m_title">6м</string>
|
||||
<string name="markets_selector_interval_7d_title">7д</string>
|
||||
<string name="markets_selector_interval_all_title">Увесь</string>
|
||||
<string name="markets_selector_interval_all_title">Усе</string>
|
||||
<string name="markets_sort_by_experienced_buyers_title">Досвідчені покупці</string>
|
||||
<string name="markets_sort_by_rating_title">За рейтингом</string>
|
||||
<string name="markets_sort_by_title">Сортувати за</string>
|
||||
|
|
@ -390,11 +391,12 @@
|
|||
<string name="markets_token_details_blockchain_site">Веб-сайт</string>
|
||||
<string name="markets_token_details_buy_pressure">Давлення покупця</string>
|
||||
<string name="markets_token_details_buy_pressure_description">Різниця між обсягом покупців та обсягом продавців</string>
|
||||
<string name="markets_token_details_buy_pressure_full">Давлення покупця</string>
|
||||
<string name="markets_token_details_circulating_supply">Циркуляційний запас</string>
|
||||
<string name="markets_token_details_circulating_supply_description">Загальна кількість монет, які доступні для торгівлі та перебувають в обігу на ринку</string>
|
||||
<string name="markets_token_details_experienced_buyers">Досвідчені покупці</string>
|
||||
<string name="markets_token_details_experienced_buyers_description">Мережеві покупці з додатковою вимогою мати не менше 100 вихідних транзакцій</string>
|
||||
<string name="markets_token_details_fully_diluted_valuation">Повністю розведена ринкова капіталізація</string>
|
||||
<string name="markets_token_details_fully_diluted_valuation">Повн. розведена кап.</string>
|
||||
<string name="markets_token_details_fully_diluted_valuation_description">Загальна теоретична вартість криптовалюти, якщо всі монети, які могли б існувати, перебувають в обігу, включаючи ті, що не перебувають в обігу в даний час</string>
|
||||
<string name="markets_token_details_genesis_date">Дата створення</string>
|
||||
<string name="markets_token_details_high">Високий</string>
|
||||
|
|
@ -661,6 +663,7 @@
|
|||
<string name="settings_forget_wallet_footer">Це призведе до видалення гаманця з застосунку. Сам гаманець можна додати знову.</string>
|
||||
<string name="settings_wallet_name_title">Ім\'я</string>
|
||||
<string name="staking_amount_requirement_error">Сума для стейкінгу має бути не менше %s</string>
|
||||
<string name="staking_amount_tron_integer_error">Сума стейкінгу буде округлена до %1$s TRX відповідно до правил мережі.</string>
|
||||
<string name="staking_claim_unstaked">Зняти кошти</string>
|
||||
<string name="staking_details_annual_percentage_rate">Процентна ставка</string>
|
||||
<string name="staking_details_annual_percentage_rate_info">Річний відсоток, який ви можете отримати, беручи участь у стейкінгу.</string>
|
||||
|
|
@ -733,6 +736,7 @@
|
|||
<string name="staking_transaction_in_progress_text">Транзакція обробляється! Наразі триває перевірка в блокчейні. Це може зайняти кілька хвилин.</string>
|
||||
<string name="staking_unbonding">Розблокування</string>
|
||||
<string name="staking_unlocked_locked">Розблокувати</string>
|
||||
<string name="staking_unlocking">Розблокування</string>
|
||||
<string name="staking_unstaked">Вивід зі стейкінгу</string>
|
||||
<string name="staking_unstaking">Зняти зі стейкінгу</string>
|
||||
<string name="staking_unstaking_item_name">Виведення зі стейкінгу %s</string>
|
||||
|
|
@ -875,6 +879,8 @@
|
|||
<string name="warning_backup_errors_title">Помилка активації</string>
|
||||
<string name="warning_beacon_chain_retirement_content">За рішенням розробників мережі BNB стандарт BEP-2 перестане підтримуватись у червні 2024 року. Щоб не втратити свої активи, їх необхідно конвертувати у стандарт BEP-20. Використовуйте функцію обміну, щоб перевести їх у мережу BNB Smart Chain.</string>
|
||||
<string name="warning_beacon_chain_retirement_title">Відключення мережі BNB Beacon Chain</string>
|
||||
<string name="warning_blocked_funds_for_fee_message">Будь ласка, поповніть рахунок на %1$s для покриття комісії мережі</string>
|
||||
<string name="warning_blocked_funds_for_fee_title">Недостатньо коштів для покриття комісії мережі</string>
|
||||
<string name="warning_button_could_be_better">Можна краще</string>
|
||||
<string name="warning_button_like_it">Вподобати</string>
|
||||
<string name="warning_button_ok">Зрозуміло!</string>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
<string name="biometric_lockout_warning_title">Too many attempts</string>
|
||||
<string name="biometric_unavailable_warning">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.</string>
|
||||
<string name="button_start_backup_process">Start backup process</string>
|
||||
<string name="buy_token_description">With your bank card or bank account</string>
|
||||
<string name="buy_token_description">Use a bank card or other payment methods</string>
|
||||
<plurals name="card_label_card_count">
|
||||
<item quantity="one">%d card</item>
|
||||
<item quantity="other">%d cards</item>
|
||||
|
|
@ -275,7 +275,7 @@
|
|||
<string name="express_token_list_empty_search">No tokens found. Please try another request</string>
|
||||
<string name="express_transaction_id">ID: %s</string>
|
||||
<string name="express_transaction_id_copied">Transaction ID copied</string>
|
||||
<string name="exсhange_token_description">With another currency in your wallet</string>
|
||||
<string name="exсhange_token_description">Convert one of your currencies to another</string>
|
||||
<string name="feedback_data_collection_message">The following information is optional. You can erase it if you don\'t want to share it.</string>
|
||||
<string name="feedback_preface_rate_negative">Tell us what functions you are missing, and we will try to help you.</string>
|
||||
<string name="feedback_preface_scan_failed">Please tell us what card do you have</string>
|
||||
|
|
@ -356,6 +356,7 @@
|
|||
<string name="markets_loading_error_title">Unable to load the data…</string>
|
||||
<string name="markets_loading_no_data_title">No data</string>
|
||||
<string name="markets_quick_actions">Quick actions</string>
|
||||
<string name="markets_search_header_title">Search through the market</string>
|
||||
<string name="markets_search_result_title">Result</string>
|
||||
<string name="markets_search_see_tokens_under_100k">See tokens under 100k USD market cap</string>
|
||||
<string name="markets_search_show_tokens">Show tokens</string>
|
||||
|
|
@ -383,6 +384,7 @@
|
|||
<string name="markets_token_details_blockchain_site">Website</string>
|
||||
<string name="markets_token_details_buy_pressure">Buy pressure</string>
|
||||
<string name="markets_token_details_buy_pressure_description">The difference between buyers volume and sellers volume</string>
|
||||
<string name="markets_token_details_buy_pressure_full">Buy pressure</string>
|
||||
<string name="markets_token_details_circulating_supply">Circulating supply</string>
|
||||
<string name="markets_token_details_circulating_supply_description">The total number of coins that are available for trading and are circulating in the market</string>
|
||||
<string name="markets_token_details_experienced_buyers">Experienced buyers</string>
|
||||
|
|
@ -517,7 +519,7 @@
|
|||
<string name="qr_scanner_camera_denied_title">Camera access denied</string>
|
||||
<string name="receive_bottom_sheet_warning_message">%1$s (%2$s) on %3$s network</string>
|
||||
<string name="receive_bottom_sheet_warning_message_full">Send only %s to this address. Sending any other currency will result in its irreversible loss.</string>
|
||||
<string name="receive_token_description">Show a QR-code or share your address</string>
|
||||
<string name="receive_token_description">Transfer funds from another wallet or exchange</string>
|
||||
<string name="referral_button_participate">Participate</string>
|
||||
<string name="referral_error_failed_to_load_info">Failed to load the information about the referral program. Please try again later.</string>
|
||||
<string name="referral_error_failed_to_load_info_with_reason">Failed to load the information about the referral program. Error code: %s. Please try again later.</string>
|
||||
|
|
@ -567,7 +569,7 @@
|
|||
<string name="scan_card_settings_message">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.</string>
|
||||
<string name="scan_card_settings_title">Get your card ready!</string>
|
||||
<string name="send_additional_field_already_included">Already included in the entered address</string>
|
||||
<string name="send_alert_fee_too_high_text">The commission amount is %s times the recommended amount. Make sure that the custom settings are correct.</string>
|
||||
<string name="send_alert_fee_too_high_text">Your commission amount is %s times higher than the recommended amount. Please review and adjust your custom settings.</string>
|
||||
<string name="send_alert_fee_too_low_text">You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue?</string>
|
||||
<string name="send_alert_transaction_failed_text">Reason: %1$s\nCode: %2$s</string>
|
||||
<string name="send_alert_transaction_failed_title">The transaction is not completed</string>
|
||||
|
|
@ -602,7 +604,7 @@
|
|||
<string name="send_notification_exceed_balance_title">Total exceeds balance</string>
|
||||
<string name="send_notification_existential_deposit_text">A balance of at least %s is required to keep your account on the blockchain to prevent security risks. This amount will remain in your balance and cannot be withdrawn.</string>
|
||||
<string name="send_notification_existential_deposit_title">Existential deposit</string>
|
||||
<string name="send_notification_fee_too_high_text">The commission amount is %s times the recommended amount. Make sure that the custom settings are correct.</string>
|
||||
<string name="send_notification_fee_too_high_text">Your commission amount is %s times higher than the recommended amount. Please review and adjust your custom settings.</string>
|
||||
<string name="send_notification_fee_too_high_title">Custom fee is high</string>
|
||||
<string name="send_notification_high_fee_text">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.</string>
|
||||
<string name="send_notification_high_fee_title">The fee is higher</string>
|
||||
|
|
@ -648,6 +650,8 @@
|
|||
<string name="staking_amount_requirement_error">The amount to stake must be at least %s</string>
|
||||
<string name="staking_amount_tron_integer_error">Staking amount will be rounded to %1$s TRX due to network rules.</string>
|
||||
<string name="staking_claim_unstaked">Claim unstaked</string>
|
||||
<string name="staking_details_account_fee">Stake account fee</string>
|
||||
<string name="staking_details_account_fee_info">A staking account is a special account where staked SOL tokens are stored. It is created when you delegate your tokens to a validator to participate in transaction validation and earn rewards. A small fee is charged for creating the staking account, which is returned after the staking is completed.</string>
|
||||
<string name="staking_details_annual_percentage_rate">Annual percentage rate</string>
|
||||
<string name="staking_details_annual_percentage_rate_info">The annual percentage return you can earn from participating in staking.</string>
|
||||
<string name="staking_details_apr">APR</string>
|
||||
|
|
@ -675,6 +679,7 @@
|
|||
<string name="staking_migrate">Migrate</string>
|
||||
<string name="staking_native">Native staking</string>
|
||||
<string name="staking_notification_claim_rewards_text">Earned rewards will be sent to your wallet and available for use immediately</string>
|
||||
<string name="staking_notification_earn_rewards_text">Stake securely and start earning your rewards</string>
|
||||
<string name="staking_notification_earn_rewards_text_daily">Stake securely and start earning daily rewards</string>
|
||||
<string name="staking_notification_earn_rewards_text_hourly">Stake securely and start earning hourly rewards</string>
|
||||
<string name="staking_notification_earn_rewards_text_monthly">Stake securely and start earning monthly rewards</string>
|
||||
|
|
@ -703,6 +708,7 @@
|
|||
<string name="staking_reward_schedule_block">Block</string>
|
||||
<string name="staking_reward_schedule_day">Day</string>
|
||||
<string name="staking_reward_schedule_each_day">Each day</string>
|
||||
<string name="staking_reward_schedule_each_minute">Each minute</string>
|
||||
<string name="staking_reward_schedule_epoch">Epoch</string>
|
||||
<string name="staking_reward_schedule_era">Era</string>
|
||||
<string name="staking_reward_schedule_hour">Hour</string>
|
||||
|
|
@ -719,6 +725,7 @@
|
|||
<string name="staking_transaction_in_progress_text">The transaction is being processed! Validation is currently underway in the blockchain. This may take a few minutes.</string>
|
||||
<string name="staking_unbonding">Unbonding</string>
|
||||
<string name="staking_unlocked_locked">Unlock</string>
|
||||
<string name="staking_unlocking">Unlocking</string>
|
||||
<string name="staking_unstaked">Unstaked</string>
|
||||
<string name="staking_unstaking">Unstaking</string>
|
||||
<string name="staking_unstaking_item_name">Unstaking assets %s</string>
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import androidx.compose.ui.text.TextStyle
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.buttons.common.*
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
// region TextButton
|
||||
/**
|
||||
|
|
@ -102,6 +102,7 @@ fun PrimaryButton(
|
|||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
size = size,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -127,6 +128,7 @@ fun PrimaryButtonIconEnd(
|
|||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
size = size,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -153,6 +155,7 @@ fun PrimaryButtonIconEndTwoLines(
|
|||
showProgress = showProgress,
|
||||
additionalText = additionalText,
|
||||
size = TangemButtonSize.TwoLines,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -176,6 +179,7 @@ fun PrimaryButtonIconStart(
|
|||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
}
|
||||
// endregion PrimaryButton
|
||||
|
|
@ -201,6 +205,7 @@ fun SecondaryButton(
|
|||
showProgress = showProgress,
|
||||
size = size,
|
||||
shape = shape,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -224,6 +229,7 @@ fun SecondaryButtonIconEnd(
|
|||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -247,6 +253,7 @@ fun SecondaryButtonIconStart(
|
|||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
}
|
||||
// endregion SecondaryButton
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
* @param captionColor caption text color
|
||||
* @param isGrayscaleImage whether to display grayscale image
|
||||
* @param iconEndRes icon to end of row
|
||||
* @param onImageError composable to show if image loading failed
|
||||
* @param extraContent extra content
|
||||
*/
|
||||
@Composable
|
||||
|
|
@ -53,6 +54,7 @@ internal fun InputRowImageBase(
|
|||
iconTint: Color = TangemTheme.colors.icon.informative,
|
||||
isGrayscaleImage: Boolean = false,
|
||||
iconEndRes: Int? = null,
|
||||
onImageError: (@Composable () -> Unit)? = null,
|
||||
extraContent: (@Composable RowScope.() -> Unit)? = null,
|
||||
) {
|
||||
Row(
|
||||
|
|
@ -63,13 +65,13 @@ internal fun InputRowImageBase(
|
|||
InputRowAsyncImage(
|
||||
imageUrl = imageUrl,
|
||||
isGrayscale = isGrayscaleImage,
|
||||
onImageError = onImageError,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.spacing36)
|
||||
.clip(TangemTheme.shapes.roundedCornersXLarge),
|
||||
)
|
||||
SpacerW12()
|
||||
}
|
||||
if (iconRes != null) {
|
||||
} else if (iconRes != null) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
* @param captionColor caption text color
|
||||
* @param isGrayscaleImage whether to display grayscale image
|
||||
* @param iconEndRes icon to end of row
|
||||
* @param onImageError composable to show if image loading failed
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
|
|
@ -52,6 +53,7 @@ fun InputRowImageInfo(
|
|||
iconTint: Color = TangemTheme.colors.icon.informative,
|
||||
isGrayscaleImage: Boolean = false,
|
||||
@DrawableRes iconEndRes: Int? = null,
|
||||
onImageError: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6),
|
||||
|
|
@ -75,6 +77,7 @@ fun InputRowImageInfo(
|
|||
captionColor = captionColor,
|
||||
isGrayscaleImage = isGrayscaleImage,
|
||||
iconEndRes = iconEndRes,
|
||||
onImageError = onImageError,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import java.math.BigDecimal
|
|||
* @param captionColor caption text color
|
||||
* @param isSelected true if selected
|
||||
* @param selectorContent selector content
|
||||
* @param onImageError composable to show if image loading failed
|
||||
*/
|
||||
@Composable
|
||||
fun InputRowImageSelector(
|
||||
|
|
@ -52,6 +53,7 @@ fun InputRowImageSelector(
|
|||
subtitleColor: Color = TangemTheme.colors.text.primary1,
|
||||
captionColor: Color = TangemTheme.colors.text.tertiary,
|
||||
isSelected: Boolean = false,
|
||||
onImageError: (@Composable () -> Unit)? = null,
|
||||
selectorContent: @Composable ((isSelected: Boolean, isEnabled: Boolean, onSelect: () -> Unit) -> Unit),
|
||||
) {
|
||||
InputRowImageBase(
|
||||
|
|
@ -60,6 +62,7 @@ fun InputRowImageSelector(
|
|||
imageUrl = imageUrl,
|
||||
subtitleColor = subtitleColor,
|
||||
captionColor = captionColor,
|
||||
onImageError = onImageError,
|
||||
modifier = modifier
|
||||
.clickable(
|
||||
onClick = onSelect,
|
||||
|
|
|
|||
|
|
@ -18,9 +18,15 @@ import com.tangem.core.ui.utils.getGreyScaleColorFilter
|
|||
* @param imageUrl url of the image
|
||||
* @param modifier modifier
|
||||
* @param isGrayscale whether to apply grayscale filter
|
||||
* @param onImageError composable to show if image loading failed
|
||||
*/
|
||||
@Composable
|
||||
internal fun InputRowAsyncImage(imageUrl: String, modifier: Modifier = Modifier, isGrayscale: Boolean = false) {
|
||||
internal fun InputRowAsyncImage(
|
||||
imageUrl: String,
|
||||
modifier: Modifier = Modifier,
|
||||
isGrayscale: Boolean = false,
|
||||
onImageError: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
val (alpha, colorFilter) = getGreyScaleColorFilter(isGrayscale = isGrayscale)
|
||||
SubcomposeAsyncImage(
|
||||
modifier = modifier,
|
||||
|
|
@ -33,7 +39,7 @@ internal fun InputRowAsyncImage(imageUrl: String, modifier: Modifier = Modifier,
|
|||
.build(),
|
||||
loading = { LoadingIcon() },
|
||||
error = {
|
||||
Box(
|
||||
onImageError?.invoke() ?: Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.tertiary,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,13 @@ import androidx.compose.foundation.lazy.LazyListState
|
|||
import androidx.compose.runtime.*
|
||||
|
||||
@Composable
|
||||
fun InfiniteListHandler(listState: LazyListState, onLoadMore: () -> Boolean, buffer: Int = 2) {
|
||||
val loadMore by remember {
|
||||
fun InfiniteListHandler(
|
||||
listState: LazyListState,
|
||||
onLoadMore: () -> Boolean,
|
||||
buffer: Int = 2,
|
||||
triggerLoadMoreCheckOnItemsCountChange: Boolean = false,
|
||||
) {
|
||||
val loadMore by remember(buffer, listState) {
|
||||
derivedStateOf {
|
||||
val layoutInfo = listState.layoutInfo
|
||||
val totalItemsNumber = layoutInfo.totalItemsCount
|
||||
|
|
@ -15,12 +20,18 @@ fun InfiniteListHandler(listState: LazyListState, onLoadMore: () -> Boolean, buf
|
|||
}
|
||||
}
|
||||
|
||||
val totalItemsCount by remember { derivedStateOf { listState.layoutInfo.totalItemsCount } }
|
||||
var emitted by remember(totalItemsCount) { mutableStateOf(false) }
|
||||
val totalItemsCount by remember(listState) { derivedStateOf { listState.layoutInfo.totalItemsCount } }
|
||||
var emitted by remember(totalItemsCount, buffer, listState) { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(loadMore) {
|
||||
if (loadMore && !emitted) {
|
||||
emitted = onLoadMore()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(totalItemsCount) {
|
||||
if (triggerLoadMoreCheckOnItemsCountChange && loadMore && !emitted) {
|
||||
emitted = onLoadMore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,9 +7,9 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -132,17 +132,6 @@ fun NotificationWithBackground(config: NotificationConfig, modifier: Modifier =
|
|||
)
|
||||
val isDarkMode = LocalIsInDarkTheme.current
|
||||
TangemButton(
|
||||
text = button?.text?.resolveReference().orEmpty(),
|
||||
icon = TangemButtonIconPosition.Start(button?.iconResId ?: R.drawable.ic_exchange_vertical_24),
|
||||
onClick = button?.onClick ?: {},
|
||||
colors = TangemButtonColors(
|
||||
backgroundColor = if (isDarkMode) Light4 else TangemTheme.colors.button.secondary,
|
||||
contentColor = Dark6,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
),
|
||||
enabled = true,
|
||||
showProgress = false,
|
||||
modifier = Modifier.constrainAs(buttonRef) {
|
||||
start.linkTo(parent.start, spacing12)
|
||||
end.linkTo(parent.end, spacing12)
|
||||
|
|
@ -154,6 +143,18 @@ fun NotificationWithBackground(config: NotificationConfig, modifier: Modifier =
|
|||
Visibility.Visible
|
||||
}
|
||||
},
|
||||
text = button?.text?.resolveReference().orEmpty(),
|
||||
icon = TangemButtonIconPosition.Start(button?.iconResId ?: R.drawable.ic_exchange_vertical_24),
|
||||
onClick = button?.onClick ?: {},
|
||||
colors = TangemButtonColors(
|
||||
backgroundColor = if (isDarkMode) Light4 else TangemTheme.colors.button.secondary,
|
||||
contentColor = Dark6,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
),
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
enabled = true,
|
||||
showProgress = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,13 @@ private fun Button(config: NotificationConfig) {
|
|||
button?.let {
|
||||
val isDarkMode = LocalIsInDarkTheme.current
|
||||
TangemButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
text = button.text.resolveReference(),
|
||||
icon = TangemButtonIconPosition.Start(button.iconResId ?: R.drawable.ic_exchange_vertical_24),
|
||||
onClick = button.onClick,
|
||||
|
|
@ -119,15 +126,9 @@ private fun Button(config: NotificationConfig) {
|
|||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
),
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
enabled = true,
|
||||
showProgress = false,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ fun String.parseToBigDecimal(decimals: Int): BigDecimal {
|
|||
}
|
||||
}
|
||||
|
||||
fun BigDecimal.parseBigDecimal(decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN): String {
|
||||
fun BigDecimal.parseBigDecimal(decimals: Int, roundingMode: RoundingMode = RoundingMode.HALF_UP): String {
|
||||
val decimalFormat = DecimalFormat().apply {
|
||||
decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault())
|
||||
isParseBigDecimal = true
|
||||
|
|
|
|||
9
core/ui/src/main/res/drawable/ic_staking_filled_18.xml
Normal file
9
core/ui/src/main/res/drawable/ic_staking_filled_18.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="18dp" android:viewportHeight="18" android:viewportWidth="18" android:width="18dp">
|
||||
|
||||
<path android:fillColor="#C9C9C9" android:fillType="evenOdd" android:pathData="M7.235,2.66C8.331,2.044 9.669,2.044 10.765,2.66L16.641,5.965C16.924,6.125 17.1,6.425 17.1,6.75C17.1,7.075 16.924,7.375 16.641,7.534L10.765,10.84C9.669,11.456 8.331,11.456 7.235,10.84L1.359,7.534C1.075,7.375 0.9,7.075 0.9,6.75C0.9,6.425 1.075,6.125 1.359,5.965L7.235,2.66ZM9.882,4.229C9.334,3.921 8.665,3.921 8.117,4.229L3.636,6.75L8.117,9.271C8.665,9.579 9.334,9.579 9.882,9.271L14.364,6.75L9.882,4.229Z"/>
|
||||
|
||||
<path android:fillColor="#C9C9C9" android:pathData="M8.117,4.229C8.665,3.921 9.334,3.921 9.882,4.229L14.364,6.75L9.882,9.271C9.334,9.579 8.665,9.579 8.117,9.271L3.636,6.75L8.117,4.229Z"/>
|
||||
|
||||
<path android:fillColor="#C9C9C9" android:fillType="evenOdd" android:pathData="M1.016,10.809C1.259,10.375 1.808,10.222 2.241,10.465L8.118,13.771C8.666,14.079 9.335,14.079 9.883,13.771L15.759,10.465C16.192,10.222 16.741,10.375 16.985,10.809C17.228,11.242 17.074,11.79 16.641,12.034L10.765,15.34C9.669,15.956 8.331,15.956 7.235,15.34L1.359,12.034C0.926,11.79 0.772,11.242 1.016,10.809Z"/>
|
||||
|
||||
</vector>
|
||||
|
|
@ -9,14 +9,17 @@ import com.tangem.data.common.currency.CryptoCurrencyFactory
|
|||
import com.tangem.data.common.currency.getNetwork
|
||||
import com.tangem.data.common.utils.retryOnError
|
||||
import com.tangem.data.markets.analytics.MarketsDataAnalyticsEvent
|
||||
import com.tangem.data.markets.converters.*
|
||||
import com.tangem.data.markets.converters.TokenChartConverter
|
||||
import com.tangem.data.markets.converters.TokenMarketInfoConverter
|
||||
import com.tangem.data.markets.converters.TokenMarketListConverter
|
||||
import com.tangem.data.markets.converters.TokenQuotesShortConverter
|
||||
import com.tangem.data.markets.converters.toRequestParam
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi.Companion.marketsQuoteFields
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.markets.*
|
||||
|
|
@ -188,16 +191,24 @@ internal class DefaultMarketsTokenRepository(
|
|||
return TokenMarketInfoConverter.convert(resultResponse)
|
||||
}
|
||||
|
||||
override suspend fun getTokenQuotes(fiatCurrencyCode: String, tokenId: String): TokenQuotes {
|
||||
// TODO change method when backend is ready
|
||||
// add error analytics event
|
||||
val response = marketsApi.getCoinMarketData(
|
||||
currency = fiatCurrencyCode,
|
||||
coinId = tokenId,
|
||||
language = "en",
|
||||
override suspend fun getTokenQuotes(fiatCurrencyCode: String, tokenId: String, tokenSymbol: String): TokenQuotes {
|
||||
// for second markets iteration we should use extended api method with all required fields
|
||||
val response = tangemTechApi.getQuotes(
|
||||
currencyId = fiatCurrencyCode,
|
||||
coinIds = tokenId,
|
||||
fields = marketsQuoteFields.joinToString(separator = ","),
|
||||
)
|
||||
|
||||
return TokenMarketInfoConverter.convert(response.getOrThrow()).quotes
|
||||
val result = catchApiErrorAndSendEvent(
|
||||
errorEvent = MarketsDataAnalyticsEvent.Details.Error(
|
||||
request = MarketsDataAnalyticsEvent.Details.Error.Request.Info,
|
||||
tokenSymbol = tokenSymbol,
|
||||
),
|
||||
) {
|
||||
response.getOrThrow()
|
||||
}
|
||||
|
||||
return TokenQuotesShortConverter.convert(tokenId, result).toFull()
|
||||
}
|
||||
|
||||
override suspend fun createCryptoCurrency(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.datasource.api.common.response.getOrThrow
|
|||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||
import com.tangem.datasource.api.markets.models.response.TokenMarketChartListResponse
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi.Companion.marketsQuoteFields
|
||||
import com.tangem.domain.markets.TokenMarket
|
||||
import com.tangem.domain.markets.TokenMarketUpdateRequest
|
||||
import com.tangem.pagination.Batch
|
||||
|
|
@ -70,7 +71,7 @@ internal class MarketsBatchUpdateFetcher(
|
|||
tangemTechApi.getQuotes(
|
||||
currencyId = updateRequest.currencyId,
|
||||
coinIds = idsToUpdate.map { it.second }.flatten().joinToString(separator = ","),
|
||||
fields = quoteFields.joinToString(separator = ","),
|
||||
fields = marketsQuoteFields.joinToString(separator = ","),
|
||||
).getOrThrow()
|
||||
}
|
||||
}
|
||||
|
|
@ -120,13 +121,4 @@ internal class MarketsBatchUpdateFetcher(
|
|||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val quoteFields = listOf(
|
||||
"price",
|
||||
"priceChange24h",
|
||||
"priceChange1w",
|
||||
"priceChange30d",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
|
|||
import com.tangem.data.markets.DefaultMarketsTokenRepository
|
||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.di.DevTangemApi
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -21,8 +20,8 @@ internal object MarketsDataModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideMarketsRepository(
|
||||
@DevTangemApi marketsApi: TangemTechMarketsApi,
|
||||
@DevTangemApi tangemTechApi: TangemTechApi,
|
||||
marketsApi: TangemTechMarketsApi,
|
||||
tangemTechApi: TangemTechApi,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ object TapWorkarounds {
|
|||
)
|
||||
|
||||
// TODO try remove if it's possible because we can configure on init sdk com.tangem.common.core.Config
|
||||
private val excludedBatches = listOf("0027", "0030", "0031", "0035", "DA88")
|
||||
private val excludedBatches = listOf("0027", "0030", "0031", "0035", "DA88", "AF56")
|
||||
|
||||
private val excludedIssuers = listOf("TTM BANK")
|
||||
|
||||
|
|
|
|||
|
|
@ -11,4 +11,17 @@ data class TokenQuotes(
|
|||
val m6ChangePercent: BigDecimal?,
|
||||
val yearChangePercent: BigDecimal?,
|
||||
val allTimeChangePercent: BigDecimal?,
|
||||
)
|
||||
)
|
||||
|
||||
fun TokenQuotes.populateWith(quotes: TokenQuotes): TokenQuotes {
|
||||
return TokenQuotes(
|
||||
currentPrice = quotes.currentPrice,
|
||||
h24ChangePercent = quotes.h24ChangePercent ?: this.h24ChangePercent,
|
||||
weekChangePercent = quotes.weekChangePercent ?: this.weekChangePercent,
|
||||
monthChangePercent = quotes.monthChangePercent ?: this.monthChangePercent,
|
||||
m3ChangePercent = quotes.m3ChangePercent ?: this.m3ChangePercent,
|
||||
m6ChangePercent = quotes.m6ChangePercent ?: this.m6ChangePercent,
|
||||
yearChangePercent = quotes.yearChangePercent ?: this.yearChangePercent,
|
||||
allTimeChangePercent = quotes.allTimeChangePercent ?: this.allTimeChangePercent,
|
||||
)
|
||||
}
|
||||
|
|
@ -7,4 +7,15 @@ data class TokenQuotesShort(
|
|||
val h24ChangePercent: BigDecimal,
|
||||
val weekChangePercent: BigDecimal,
|
||||
val monthChangePercent: BigDecimal,
|
||||
)
|
||||
|
||||
fun TokenQuotesShort.toFull() = TokenQuotes(
|
||||
currentPrice = currentPrice,
|
||||
h24ChangePercent = h24ChangePercent,
|
||||
weekChangePercent = weekChangePercent,
|
||||
monthChangePercent = monthChangePercent,
|
||||
m3ChangePercent = null,
|
||||
m6ChangePercent = null,
|
||||
yearChangePercent = null,
|
||||
allTimeChangePercent = null,
|
||||
)
|
||||
|
|
@ -7,11 +7,16 @@ import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
|||
class GetTokenFullQuotesUseCase(
|
||||
private val marketsTokenRepository: MarketsTokenRepository,
|
||||
) {
|
||||
suspend operator fun invoke(appCurrency: AppCurrency, tokenId: String): Either<Unit, TokenQuotes> {
|
||||
suspend operator fun invoke(
|
||||
appCurrency: AppCurrency,
|
||||
tokenId: String,
|
||||
tokenSymbol: String,
|
||||
): Either<Unit, TokenQuotes> {
|
||||
return Either.catch {
|
||||
marketsTokenRepository.getTokenQuotes(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
tokenId = tokenId,
|
||||
tokenSymbol = tokenSymbol,
|
||||
)
|
||||
}.mapLeft {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ interface MarketsTokenRepository {
|
|||
languageCode: String,
|
||||
): TokenMarketInfo
|
||||
|
||||
suspend fun getTokenQuotes(fiatCurrencyCode: String, tokenId: String): TokenQuotes
|
||||
suspend fun getTokenQuotes(fiatCurrencyCode: String, tokenId: String, tokenSymbol: String): TokenQuotes
|
||||
|
||||
suspend fun createCryptoCurrency(
|
||||
userWalletId: UserWalletId,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ internal class CurrencyStatusOperations(
|
|||
(yieldBalance as? YieldBalance.Data)?.address == status.address.defaultAddress.value
|
||||
val currentYieldBalance = yieldBalance.takeIf { isCurrentAddressStaking }
|
||||
return when {
|
||||
ignoreQuote -> CryptoCurrencyStatus.NoQuote(
|
||||
ignoreQuote || quote == null -> CryptoCurrencyStatus.NoQuote(
|
||||
amount = amount,
|
||||
hasCurrentNetworkTransactions = hasCurrentNetworkTransactions,
|
||||
pendingTransactions = currentTransactions,
|
||||
|
|
@ -70,15 +70,14 @@ internal class CurrencyStatusOperations(
|
|||
)
|
||||
currency is CryptoCurrency.Token && currency.isCustom -> CryptoCurrencyStatus.Custom(
|
||||
amount = amount,
|
||||
fiatAmount = calculateFiatAmountOrNull(amount, quote?.fiatRate),
|
||||
fiatRate = quote?.fiatRate,
|
||||
priceChange = quote?.priceChange,
|
||||
fiatAmount = calculateFiatAmountOrNull(amount, quote.fiatRate),
|
||||
fiatRate = quote.fiatRate,
|
||||
priceChange = quote.priceChange,
|
||||
hasCurrentNetworkTransactions = hasCurrentNetworkTransactions,
|
||||
pendingTransactions = currentTransactions,
|
||||
networkAddress = status.address,
|
||||
yieldBalance = currentYieldBalance,
|
||||
)
|
||||
quote == null -> CryptoCurrencyStatus.Loading
|
||||
else -> CryptoCurrencyStatus.Loaded(
|
||||
amount = amount,
|
||||
fiatAmount = calculateFiatAmount(amount, quote.fiatRate),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import arrow.core.raise.either
|
|||
import arrow.core.raise.ensure
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import arrow.core.toNonEmptyListOrNull
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class TokenListSortingOperations(
|
||||
|
|
@ -83,7 +85,7 @@ internal class TokenListSortingOperations(
|
|||
return if (isAnyTokenLoading) {
|
||||
tokens
|
||||
} else {
|
||||
tokens.sortedByDescending { it.value.fiatAmount ?: BigDecimal.ZERO }
|
||||
tokens.sortedByDescending { it.getTotalBalance() }
|
||||
.toNonEmptyListOrNull()
|
||||
?: error("Tokens can not be empty here")
|
||||
}
|
||||
|
|
@ -92,12 +94,20 @@ internal class TokenListSortingOperations(
|
|||
private fun sortGroupsByBalance(groupsWithSortedTokens: NonEmptyList<NetworkGroup>): NonEmptyList<NetworkGroup> {
|
||||
return groupsWithSortedTokens
|
||||
.sortedByDescending { group ->
|
||||
group.currencies.sumOf { it.value.fiatAmount ?: BigDecimal.ZERO }
|
||||
group.currencies.sumOf { it.getTotalBalance() }
|
||||
}
|
||||
.toNonEmptyListOrNull()
|
||||
?: error("Tokens can not be empty here")
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getTotalBalance(): BigDecimal {
|
||||
val yieldBalance = value.yieldBalance as? YieldBalance.Data
|
||||
val totalYieldBalance = yieldBalance?.getTotalWithRewardsStakingBalance().orZero()
|
||||
val totalFiatYieldBalance = totalYieldBalance.multiply(value.fiatRate.orZero())
|
||||
|
||||
return value.fiatAmount?.plus(totalFiatYieldBalance).orZero()
|
||||
}
|
||||
|
||||
sealed class Error {
|
||||
|
||||
object EmptyTokens : Error()
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
val result = getTokenFullQuotesUseCase(
|
||||
tokenId = params.token.id,
|
||||
appCurrency = currentAppCurrency.value,
|
||||
tokenSymbol = params.token.symbol,
|
||||
)
|
||||
|
||||
result.onRight { res ->
|
||||
|
|
@ -389,9 +390,11 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun updateQuotes(newQuotes: TokenQuotes) {
|
||||
quotesStateUpdater.updateQuotes(newQuotes)
|
||||
val populatedNewQuotes = currentQuotes.value.populateWith(newQuotes)
|
||||
|
||||
val percent = newQuotes
|
||||
quotesStateUpdater.updateQuotes(newQuotes = populatedNewQuotes)
|
||||
|
||||
val percent = populatedNewQuotes
|
||||
.getPercentByInterval(interval = state.value.selectedInterval)
|
||||
|
||||
chartDataProducer.runTransaction {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import androidx.compose.ui.layout.onGloballyPositioned
|
|||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
|
|
@ -162,6 +163,7 @@ private fun ContinueButton(
|
|||
showProgress = false,
|
||||
size = TangemButtonSize.Default,
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
onClick = onClick,
|
||||
animateContentChange = true,
|
||||
)
|
||||
|
|
@ -200,10 +202,13 @@ private fun NetworkSelection(state: SelectNetworkUM, modifier: Modifier = Modifi
|
|||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.weight(1f, fill = false)
|
||||
.alignByBaseline(),
|
||||
text = state.tokenName,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
SpacerW6()
|
||||
Text(
|
||||
|
|
@ -213,6 +218,8 @@ private fun NetworkSelection(state: SelectNetworkUM, modifier: Modifier = Modifi
|
|||
text = state.tokenCurrencySymbol,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
overflow = TextOverflow.Visible,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,14 +62,14 @@ internal class PreviewAddToPortfolioBSContentProvider : PreviewParameterProvider
|
|||
selectedWallet = userWallet,
|
||||
selectNetworkUM = SelectNetworkUM(
|
||||
tokenId = "etherium",
|
||||
tokenName = "Etherium",
|
||||
tokenName = "Etherium Etherium Etherium Etherium",
|
||||
tokenCurrencySymbol = "ETH",
|
||||
networks = persistentListOf(
|
||||
blockchainRow.copy(
|
||||
type = "MAIN",
|
||||
isMainNetwork = true,
|
||||
isSelected = true,
|
||||
),
|
||||
).copy(name = "Etherium Etherium Etherium Etherium"),
|
||||
*Array(25) { blockchainRow },
|
||||
),
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM
|
|||
import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
|
|
@ -111,54 +110,55 @@ internal class MarketsListUMStateManager(
|
|||
|
||||
private fun MarketsListUM.updateItems(newItems: ImmutableList<MarketsListItemUM>): MarketsListUM {
|
||||
val currentState = this
|
||||
val isNextPageInSearch = isInSearchState && (this.list as? ListUM.Content)?.showUnder100kTokens == true
|
||||
var searchUiItemsCached: ImmutableList<MarketsListItemUM> = persistentListOf()
|
||||
|
||||
val items = when {
|
||||
isInSearchState && isNextPageInSearch.not() -> {
|
||||
searchUiItemsCached = newItems
|
||||
val filtered = newItems.filter { item -> item.isUnder100kMarketCap.not() }.toImmutableList()
|
||||
if (isInSearchMode.not() || currentState.showUnder100kButtonAlreadyPressed()) {
|
||||
val itemsWithFilteredPriceChange = newItems.filterPriceChangeByVisibility()
|
||||
|
||||
if (filtered.size == newItems.size) {
|
||||
return currentState.copy(list = generalContentState(newItems))
|
||||
} else {
|
||||
filtered
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
searchUiItemsCached = persistentListOf()
|
||||
newItems
|
||||
}
|
||||
return currentState.copy(
|
||||
list = generalContentState(itemsWithFilteredPriceChange)
|
||||
.copy(
|
||||
showUnder100kTokensNotificationWasHidden = currentState.showUnder100kButtonAlreadyPressed(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val itemsWithFilteredPriceChange = items.filterPriceChangeByVisibility()
|
||||
// Search state cases
|
||||
|
||||
return currentState.copy(
|
||||
list = ListUM.Content(
|
||||
items = itemsWithFilteredPriceChange,
|
||||
loadMore = onLoadMoreUiItems,
|
||||
visibleIdsChanged = visibleItemsChanged,
|
||||
showUnder100kTokens = isInSearchState.not() || isNextPageInSearch,
|
||||
onShowTokensUnder100kClicked = {
|
||||
if (searchUiItemsCached.isNotEmpty()) {
|
||||
val filtered = newItems.filter { item -> item.isUnder100kMarketCap.not() }
|
||||
.toImmutableList()
|
||||
.filterPriceChangeByVisibility()
|
||||
|
||||
if (filtered.size != newItems.size) {
|
||||
val searchUiItemsCached = newItems.filterPriceChangeByVisibility()
|
||||
|
||||
return currentState.copy(
|
||||
list = generalContentState(filtered).copy(
|
||||
showUnder100kTokensNotificationWasHidden = false,
|
||||
showUnder100kTokensNotification = true,
|
||||
onShowTokensUnder100kClicked = {
|
||||
state.update { s ->
|
||||
if (s.list is ListUM.Content) {
|
||||
(s.list as? ListUM.Content)?.let {
|
||||
s.copy(
|
||||
list = s.list.copy(
|
||||
items = searchUiItemsCached,
|
||||
showUnder100kTokens = true,
|
||||
showUnder100kTokensNotification = false,
|
||||
showUnder100kTokensNotificationWasHidden = true,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
s
|
||||
}
|
||||
} ?: s
|
||||
}
|
||||
}
|
||||
},
|
||||
triggerScrollReset = consumedEvent(),
|
||||
onItemClick = onTokenClick,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
} else {
|
||||
return currentState.copy(
|
||||
list = generalContentState(newItems.filterPriceChangeByVisibility()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MarketsListUM.showUnder100kButtonAlreadyPressed(): Boolean {
|
||||
return this.list is ListUM.Content && this.isInSearchMode && this.list.showUnder100kTokensNotificationWasHidden
|
||||
}
|
||||
|
||||
// Show price change animation for visible items only
|
||||
|
|
@ -182,10 +182,11 @@ internal class MarketsListUMStateManager(
|
|||
items = newItems,
|
||||
loadMore = onLoadMoreUiItems,
|
||||
visibleIdsChanged = visibleItemsChanged,
|
||||
showUnder100kTokens = true,
|
||||
showUnder100kTokensNotification = false,
|
||||
onShowTokensUnder100kClicked = {},
|
||||
triggerScrollReset = consumedEvent(),
|
||||
onItemClick = onTokenClick,
|
||||
showUnder100kTokensNotificationWasHidden = false,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -301,7 +301,8 @@ private fun Preview() {
|
|||
item.copy(id = index.toString())
|
||||
}
|
||||
.toImmutableList(),
|
||||
showUnder100kTokens = false,
|
||||
showUnder100kTokensNotification = false,
|
||||
showUnder100kTokensNotificationWasHidden = false,
|
||||
loadMore = {},
|
||||
visibleIdsChanged = {},
|
||||
onShowTokensUnder100kClicked = {},
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM
|
|||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val LOAD_NEXT_PAGE_ON_END_INDEX = 50
|
||||
private const val LOAD_NEXT_PAGE_ON_END_INDEX_SEARCH = 25
|
||||
private const val TOKEN_LAZY_LIST_ID_SEPARATOR = "***"
|
||||
|
||||
@Composable
|
||||
|
|
@ -95,7 +96,7 @@ internal fun MarketsListLazyColumn(
|
|||
)
|
||||
}
|
||||
|
||||
if (isInSearchMode && state.showUnder100kTokens.not()) {
|
||||
if (isInSearchMode && state.showUnder100kTokensNotification) {
|
||||
item(key = "show tokens under 100k".hashCode()) {
|
||||
ShowTokensUnder100kItem(
|
||||
onShowTokensClick = state.onShowTokensUnder100kClicked,
|
||||
|
|
@ -112,10 +113,15 @@ internal fun MarketsListLazyColumn(
|
|||
|
||||
InfiniteListHandler(
|
||||
listState = lazyListState,
|
||||
buffer = LOAD_NEXT_PAGE_ON_END_INDEX,
|
||||
buffer = if (isInSearchMode) {
|
||||
LOAD_NEXT_PAGE_ON_END_INDEX_SEARCH
|
||||
} else {
|
||||
LOAD_NEXT_PAGE_ON_END_INDEX
|
||||
},
|
||||
triggerLoadMoreCheckOnItemsCountChange = true,
|
||||
onLoadMore = remember(state) {
|
||||
{
|
||||
if (state is ListUM.Content && state.showUnder100kTokens) {
|
||||
if (state is ListUM.Content && state.showUnder100kTokensNotification.not()) {
|
||||
state.loadMore()
|
||||
true
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
|||
chardData = MarketChartRawData(
|
||||
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
||||
),
|
||||
isUnder100kMarketCap = false,
|
||||
),
|
||||
MarketsListItemUM(
|
||||
id = "1",
|
||||
|
|
@ -34,6 +35,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
|||
trendPercentText = "12.43%",
|
||||
trendType = PriceChangeType.NEUTRAL,
|
||||
chardData = null,
|
||||
isUnder100kMarketCap = false,
|
||||
),
|
||||
MarketsListItemUM(
|
||||
id = "1",
|
||||
|
|
@ -48,6 +50,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
|||
chardData = MarketChartRawData(
|
||||
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
||||
),
|
||||
isUnder100kMarketCap = false,
|
||||
),
|
||||
MarketsListItemUM(
|
||||
id = "1",
|
||||
|
|
@ -62,6 +65,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
|||
chardData = MarketChartRawData(
|
||||
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
||||
),
|
||||
isUnder100kMarketCap = false,
|
||||
),
|
||||
MarketsListItemUM(
|
||||
id = "1",
|
||||
|
|
@ -76,6 +80,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
|||
chardData = MarketChartRawData(
|
||||
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
||||
),
|
||||
isUnder100kMarketCap = false,
|
||||
),
|
||||
MarketsListItemUM(
|
||||
id = "1",
|
||||
|
|
@ -90,6 +95,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
|||
chardData = MarketChartRawData(
|
||||
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
||||
),
|
||||
isUnder100kMarketCap = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -17,7 +17,7 @@ data class MarketsListItemUM(
|
|||
val trendPercentText: String,
|
||||
val trendType: PriceChangeType,
|
||||
val chardData: MarketChartRawData?,
|
||||
val isUnder100kMarketCap: Boolean = false,
|
||||
val isUnder100kMarketCap: Boolean,
|
||||
) {
|
||||
val chartType: MarketChartLook.Type = when (trendType) {
|
||||
PriceChangeType.UP -> MarketChartLook.Type.Growing
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@ sealed class ListUM {
|
|||
|
||||
data class Content(
|
||||
val items: ImmutableList<MarketsListItemUM>,
|
||||
val showUnder100kTokens: Boolean,
|
||||
val showUnder100kTokensNotification: Boolean,
|
||||
val showUnder100kTokensNotificationWasHidden: Boolean,
|
||||
val loadMore: () -> Unit,
|
||||
val visibleIdsChanged: (List<String>) -> Unit,
|
||||
val onShowTokensUnder100kClicked: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ private fun SendNavigationButton(
|
|||
}
|
||||
}
|
||||
TangemButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(buttonTextId),
|
||||
icon = buttonIcon,
|
||||
enabled = isButtonEnabled,
|
||||
|
|
@ -135,8 +136,8 @@ private fun SendNavigationButton(
|
|||
buttonClick()
|
||||
},
|
||||
showProgress = false,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -274,7 +275,7 @@ private fun isButtonEnabled(currentState: SendUiCurrentScreen, uiState: SendUiSt
|
|||
|
||||
private fun getTokenFeeSendingText(feeState: SendStates.FeeState, fee: Fee.Tron, sendingValue: String): TextReference {
|
||||
val suffix = when {
|
||||
fee.feeEnergy == 0L -> {
|
||||
fee.remainingEnergy == 0L -> {
|
||||
resourceReference(
|
||||
R.string.send_summary_transaction_description_suffix_including,
|
||||
wrappedList(feeState.getFiatValue()),
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ internal class AddStakingNotificationsTransformer(
|
|||
isSubtractAvailable = isSubtractAvailable,
|
||||
reduceAmountBy = reduceAmountBy,
|
||||
)
|
||||
val minimumRequirement = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum.orZero()
|
||||
val sendingAmount = if (isEnterAction) {
|
||||
checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable = isSubtractAvailable,
|
||||
|
|
@ -84,7 +85,7 @@ internal class AddStakingNotificationsTransformer(
|
|||
} else {
|
||||
// No amount is taken from account balance on exit or pending actions
|
||||
BigDecimal.ZERO
|
||||
}
|
||||
}.max(minimumRequirement)
|
||||
|
||||
val notifications = buildList {
|
||||
// errors
|
||||
|
|
@ -106,7 +107,7 @@ internal class AddStakingNotificationsTransformer(
|
|||
amountState = amountState,
|
||||
feeState = feeState,
|
||||
sendingAmount = sendingAmount,
|
||||
isFeeCoverage = isFeeCoverage && isEnterAction,
|
||||
isFeeCoverage = isFeeCoverage && isEnterAction && !sendingAmount.equals(minimumRequirement),
|
||||
)
|
||||
|
||||
addInfoNotifications(prevState)
|
||||
|
|
@ -223,11 +224,10 @@ internal class AddStakingNotificationsTransformer(
|
|||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
onClick: (CryptoCurrency) -> Unit,
|
||||
) {
|
||||
val minimumRequirement = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
if (!isSubtractionAvailable) return
|
||||
|
||||
val showNotification = sendingAmount + feeAmount > balance - minimumRequirement.orZero()
|
||||
val showNotification = sendingAmount + feeAmount > balance
|
||||
if (showNotification) {
|
||||
val notification = if (actionType == StakingActionCommonType.ENTER) {
|
||||
NotificationUM.Error.TotalExceedsBalance
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ internal class SetConfirmationStateLoadingTransformer(
|
|||
availableValidators = yield.validators,
|
||||
),
|
||||
notifications = persistentListOf(),
|
||||
footerText = getFooter(prevState),
|
||||
footerText = getFooter(prevState, chosenValidator),
|
||||
transactionDoneState = TransactionDoneState.Empty,
|
||||
pendingAction = possibleConfirmationState?.pendingAction,
|
||||
pendingActions = possibleConfirmationState?.pendingActions,
|
||||
|
|
@ -45,14 +45,12 @@ internal class SetConfirmationStateLoadingTransformer(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getFooter(state: StakingUiState): TextReference {
|
||||
private fun getFooter(state: StakingUiState, validator: Yield.Validator): TextReference {
|
||||
val amountState = state.amountState as? AmountState.Data
|
||||
val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val validatorState = confirmationState?.validatorState as? ValidatorState.Content
|
||||
|
||||
val isEnterAction = state.actionType == StakingActionCommonType.ENTER
|
||||
|
||||
val apr = validatorState?.chosenValidator?.apr.orZero()
|
||||
val apr = validator.apr.orZero()
|
||||
val amountDecimal = amountState?.amountTextField?.fiatAmount?.value
|
||||
val potentialReward = amountDecimal?.multiply(apr)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
|||
import com.tangem.features.staking.impl.presentation.state.converters.RewardsValidatorStateConverter
|
||||
import com.tangem.features.staking.impl.presentation.state.converters.YieldBalancesConverter
|
||||
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isCosmos
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isPolkadot
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.isNullOrZero
|
||||
|
|
@ -201,12 +202,12 @@ internal class SetInitialDataStateTransformer(
|
|||
private fun createRewardScheduleItem(
|
||||
rewardSchedule: Yield.Metadata.RewardSchedule,
|
||||
): RoundedListWithDividersItemData? {
|
||||
val endTextId = rewardScheduleResources[rewardSchedule] ?: return null
|
||||
val endTextReference = getRewardScheduleText(rewardSchedule) ?: return null
|
||||
|
||||
return RoundedListWithDividersItemData(
|
||||
id = R.string.staking_details_reward_schedule,
|
||||
startText = TextReference.Res(R.string.staking_details_reward_schedule),
|
||||
endText = TextReference.Res(endTextId),
|
||||
startText = resourceReference(R.string.staking_details_reward_schedule),
|
||||
endText = endTextReference,
|
||||
iconClick = { clickIntents.onInfoClick(InfoType.REWARD_SCHEDULE) },
|
||||
)
|
||||
}
|
||||
|
|
@ -252,21 +253,29 @@ internal class SetInitialDataStateTransformer(
|
|||
return resourceReference(R.string.common_range, wrappedList(formattedMinApr, formattedMaxApr))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val EQUALITY_THRESHOLD = BigDecimal(1E-10)
|
||||
private fun getRewardScheduleText(rewardSchedule: Yield.Metadata.RewardSchedule): TextReference? {
|
||||
return when (rewardSchedule) {
|
||||
Yield.Metadata.RewardSchedule.BLOCK -> {
|
||||
val networkId = cryptoCurrencyStatusProvider().currency.network.id.value
|
||||
when {
|
||||
isCosmos(networkId) -> resourceReference(R.string.staking_reward_schedule_each_minute)
|
||||
else -> resourceReference(R.string.staking_reward_schedule_each_day)
|
||||
}
|
||||
}
|
||||
Yield.Metadata.RewardSchedule.WEEK -> resourceReference(R.string.staking_reward_schedule_week)
|
||||
Yield.Metadata.RewardSchedule.HOUR -> resourceReference(R.string.staking_reward_schedule_hour)
|
||||
Yield.Metadata.RewardSchedule.DAY -> resourceReference(R.string.staking_reward_schedule_each_day)
|
||||
Yield.Metadata.RewardSchedule.MONTH -> resourceReference(R.string.staking_reward_schedule_month)
|
||||
Yield.Metadata.RewardSchedule.ERA -> resourceReference(R.string.staking_reward_schedule_era)
|
||||
Yield.Metadata.RewardSchedule.EPOCH -> resourceReference(R.string.staking_reward_schedule_epoch)
|
||||
Yield.Metadata.RewardSchedule.UNKNOWN -> null
|
||||
}
|
||||
}
|
||||
|
||||
private val rewardScheduleResources = mapOf(
|
||||
Yield.Metadata.RewardSchedule.BLOCK to R.string.staking_reward_schedule_each_day,
|
||||
Yield.Metadata.RewardSchedule.WEEK to R.string.staking_reward_schedule_week,
|
||||
Yield.Metadata.RewardSchedule.HOUR to R.string.staking_reward_schedule_hour,
|
||||
Yield.Metadata.RewardSchedule.DAY to R.string.staking_reward_schedule_each_day,
|
||||
Yield.Metadata.RewardSchedule.MONTH to R.string.staking_reward_schedule_month,
|
||||
Yield.Metadata.RewardSchedule.ERA to R.string.staking_reward_schedule_era,
|
||||
Yield.Metadata.RewardSchedule.EPOCH to R.string.staking_reward_schedule_epoch,
|
||||
Yield.Metadata.RewardSchedule.UNKNOWN to null,
|
||||
)
|
||||
private companion object {
|
||||
val EQUALITY_THRESHOLD = BigDecimal(1E-10)
|
||||
|
||||
private val rewardClaimingResources = mapOf(
|
||||
val rewardClaimingResources = mapOf(
|
||||
Yield.Metadata.RewardClaiming.MANUAL to R.string.staking_reward_claiming_manual,
|
||||
Yield.Metadata.RewardClaiming.AUTO to R.string.staking_reward_claiming_auto,
|
||||
Yield.Metadata.RewardSchedule.UNKNOWN to null,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,12 @@ internal fun checkAndCalculateSubtractedAmount(
|
|||
reduceAmountBy = reduceAmountBy,
|
||||
)
|
||||
return if (isFeeCoverage) {
|
||||
balance.minus(reduceAmountBy).minus(feeValueRounded)
|
||||
val reducedAmount = balance.minus(reduceAmountBy).minus(feeValueRounded)
|
||||
if (isTron(cryptoCurrencyStatus.currency.network.id.value)) {
|
||||
reducedAmount.setScale(0, RoundingMode.DOWN)
|
||||
} else {
|
||||
reducedAmount
|
||||
}
|
||||
} else {
|
||||
amountValue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ internal fun StakingClaimRewardsValidatorContent(
|
|||
infoTitle = item.fiatAmount,
|
||||
infoSubtitle = item.cryptoAmount,
|
||||
imageUrl = item.validator?.image.orEmpty(),
|
||||
onImageError = { ValidatorImagePlaceholder() },
|
||||
modifier = modifier
|
||||
.roundedShapeItemDecoration(index, state.rewards.lastIndex, false)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ import com.tangem.features.staking.impl.presentation.state.previewdata.InitialSt
|
|||
import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub
|
||||
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
|
||||
import com.tangem.utils.StringsSigns.DOT
|
||||
import com.tangem.utils.StringsSigns.PLUS
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
||||
private const val BANNER_BLOCK_KEY = "BannerBlock"
|
||||
|
|
@ -231,8 +230,6 @@ private fun StakingRewardBlock(
|
|||
val (text, textColor) = when (rewardBlockType) {
|
||||
RewardBlockType.Rewards -> {
|
||||
annotatedReference {
|
||||
append(PLUS)
|
||||
appendSpace()
|
||||
append(rewardFiat.orMaskWithStars(isBalanceHidden))
|
||||
appendSpace()
|
||||
append(DOT)
|
||||
|
|
@ -282,6 +279,7 @@ private fun ActiveStakingBlock(
|
|||
imageUrl = balance.getImage(),
|
||||
iconRes = icon,
|
||||
iconTint = iconTint,
|
||||
onImageError = { ValidatorImagePlaceholder() },
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.clickable(
|
||||
|
|
|
|||
|
|
@ -3,10 +3,7 @@ package com.tangem.features.staking.impl.presentation.ui
|
|||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -95,6 +92,7 @@ internal fun StakingValidatorListContent(
|
|||
)
|
||||
}
|
||||
},
|
||||
onImageError = { ValidatorImagePlaceholder() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.features.staking.impl.presentation.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.staking.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun ValidatorImagePlaceholder() {
|
||||
Icon(
|
||||
painter = rememberVectorPainter(ImageVector.vectorResource(R.drawable.ic_staking_filled_18)),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.inactive,
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.icon.primary1, CircleShape)
|
||||
.padding(TangemTheme.dimens.size9),
|
||||
)
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.features.staking.impl.presentation.state.ValidatorState
|
||||
import com.tangem.features.staking.impl.presentation.ui.ValidatorImagePlaceholder
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
||||
@Composable
|
||||
|
|
@ -45,6 +46,7 @@ internal fun ValidatorBlock(validatorState: ValidatorState, onClick: () -> Unit)
|
|||
)
|
||||
},
|
||||
imageUrl = validatorState.chosenValidator.image.orEmpty(),
|
||||
onImageError = { ValidatorImagePlaceholder() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
|
|||
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.*
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getBalance
|
||||
|
|
@ -20,8 +21,9 @@ internal class TokenDetailsBalanceSelectStateConverter(
|
|||
if (stakingBlocksState !is StakingBlockUM.Staked) return this
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() ?: return this
|
||||
|
||||
val stakingCryptoAmount = stakingBlocksState.cryptoAmount
|
||||
val stakingFiatAmount = stakingBlocksState.fiatAmount
|
||||
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
|
||||
val stakingCryptoAmount = yieldBalance?.getTotalWithRewardsStakingBalance()
|
||||
val stakingFiatAmount = stakingCryptoAmount?.let { cryptoCurrencyStatus.value.fiatRate?.multiply(it) }
|
||||
|
||||
copy(
|
||||
tokenBalanceBlockState = if (tokenBalanceBlockState is TokenDetailsBalanceBlockState.Content) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.*
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsTxHistoryTransactionStateConverter
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getBalance
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getStringResourceId
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
|
||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
|
|
@ -153,8 +152,9 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
StakingBlockUM.Error(iconState = iconState)
|
||||
}
|
||||
else -> {
|
||||
val stakingRewardAmount = yieldBalance?.getRewardStakingBalance()
|
||||
val stakingFiatAmount = stakingCryptoAmount?.let { status.value.fiatRate?.multiply(it) }
|
||||
val fiatRate = status.value.fiatRate
|
||||
val stakingRewardAmount = yieldBalance?.getRewardStakingBalance()?.let { fiatRate?.multiply(it) }
|
||||
val stakingFiatAmount = stakingCryptoAmount?.let { fiatRate?.multiply(it) }
|
||||
|
||||
getStakedState(
|
||||
status = status,
|
||||
|
|
@ -203,7 +203,7 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
formatArgs = wrappedList(apr),
|
||||
),
|
||||
subtitleText = resourceReference(
|
||||
id = stakingEntryInfo.rewardSchedule.getStringResourceId(),
|
||||
id = R.string.staking_notification_earn_rewards_text,
|
||||
formatArgs = wrappedList(stakingEntryInfo.tokenSymbol),
|
||||
),
|
||||
iconState = iconState,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.tangem.domain.staking.model.StakingEntryInfo
|
|||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getStringResourceId
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.utils.Provider
|
||||
|
|
@ -39,7 +38,7 @@ internal class TokenStakingStateConverter(
|
|||
formatArgs = wrappedList(apr),
|
||||
),
|
||||
subtitleText = resourceReference(
|
||||
id = stakingEntryInfo.rewardSchedule.getStringResourceId(),
|
||||
id = R.string.staking_notification_earn_rewards_text,
|
||||
formatArgs = wrappedList(stakingEntryInfo.tokenSymbol),
|
||||
),
|
||||
iconState = iconState,
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.utils
|
||||
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
|
||||
internal fun Yield.Metadata.RewardSchedule.getStringResourceId(): Int {
|
||||
return when (this) {
|
||||
Yield.Metadata.RewardSchedule.BLOCK,
|
||||
Yield.Metadata.RewardSchedule.DAY,
|
||||
Yield.Metadata.RewardSchedule.ERA,
|
||||
Yield.Metadata.RewardSchedule.EPOCH,
|
||||
-> R.string.staking_notification_earn_rewards_text_daily
|
||||
|
||||
Yield.Metadata.RewardSchedule.HOUR,
|
||||
-> R.string.staking_notification_earn_rewards_text_hourly
|
||||
|
||||
Yield.Metadata.RewardSchedule.WEEK,
|
||||
-> R.string.staking_notification_earn_rewards_text_weekly
|
||||
|
||||
Yield.Metadata.RewardSchedule.MONTH,
|
||||
-> R.string.staking_notification_earn_rewards_text_monthly
|
||||
|
||||
else
|
||||
-> R.string.staking_notification_earn_rewards_text_daily
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import androidx.compose.runtime.*
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.geometry.*
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
|
|
@ -386,11 +387,13 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
LocalMainBottomSheetColor provides remember { mutableStateOf(backgroundPrimary) },
|
||||
) {
|
||||
val backgroundColor = LocalMainBottomSheetColor.current
|
||||
var isSearchFieldFocused by remember { mutableStateOf(false) }
|
||||
|
||||
BottomSheetStateEffects(
|
||||
bottomSheetState = bottomSheetState,
|
||||
alertConfig = alertConfig,
|
||||
onBottomSheetStateChange = onBottomSheetStateChange,
|
||||
isSearchFieldFocused = isSearchFieldFocused,
|
||||
)
|
||||
|
||||
TangemBottomSheetScaffold(
|
||||
|
|
@ -407,7 +410,6 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
sheetContainerColor = backgroundColor.value,
|
||||
scaffoldState = scaffoldState,
|
||||
sheetPeekHeight = peekHeight,
|
||||
sheetTonalElevation = 8.dp,
|
||||
sheetShadowElevation = 8.dp,
|
||||
sheetShape = TangemTheme.shapes.bottomSheetLarge,
|
||||
sheetContent = {
|
||||
|
|
@ -423,7 +425,14 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
modifier = Modifier.sizeIn(maxHeight = maxHeight - statusBarHeight),
|
||||
) {
|
||||
Hand(Modifier.drawBehind { drawRect(backgroundColor.value) })
|
||||
bottomSheetContent()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.onFocusChanged {
|
||||
isSearchFieldFocused = it.isFocused
|
||||
},
|
||||
) {
|
||||
bottomSheetContent()
|
||||
}
|
||||
}
|
||||
},
|
||||
content = { paddingValues ->
|
||||
|
|
@ -462,7 +471,10 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
color = BottomSheetDefaults.ScrimColor,
|
||||
visible = bottomSheetState.targetValue == TangemSheetValue.Expanded ||
|
||||
state.showMarketsOnboarding,
|
||||
onDismissRequest = { coroutineScope.launch { bottomSheetState.partialExpand() } },
|
||||
onDismissRequest = {
|
||||
coroutineScope.launch { bottomSheetState.partialExpand() }
|
||||
state.onDismissMarketsOnboarding()
|
||||
},
|
||||
)
|
||||
|
||||
MarketsTooltip(
|
||||
|
|
@ -486,7 +498,6 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun MarketsTooltip(
|
||||
availableHeight: Dp,
|
||||
|
|
@ -640,11 +651,27 @@ private fun BottomSheetStateEffects(
|
|||
bottomSheetState: TangemSheetState,
|
||||
alertConfig: WalletAlertState?,
|
||||
onBottomSheetStateChange: (BottomSheetState) -> Unit,
|
||||
isSearchFieldFocused: Boolean,
|
||||
) {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val navigationBarColor = TangemTheme.colors.background.primary
|
||||
|
||||
LaunchedEffect(key1 = bottomSheetState.targetValue, navigationBarColor) {
|
||||
LaunchedEffect(navigationBarColor) {
|
||||
delay(timeMillis = 100)
|
||||
when (bottomSheetState.currentValue) {
|
||||
TangemSheetValue.Hidden,
|
||||
TangemSheetValue.Expanded,
|
||||
-> systemUiController.setNavigationBarColor(
|
||||
color = Color.Transparent,
|
||||
darkIcons = navigationBarColor.luminance() > 0.5f,
|
||||
navigationBarContrastEnforced = true,
|
||||
)
|
||||
TangemSheetValue.PartiallyExpanded,
|
||||
-> systemUiController.setNavigationBarColor(navigationBarColor)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(bottomSheetState.targetValue, navigationBarColor) {
|
||||
when (bottomSheetState.targetValue) {
|
||||
TangemSheetValue.Hidden,
|
||||
TangemSheetValue.Expanded,
|
||||
|
|
@ -673,7 +700,7 @@ private fun BottomSheetStateEffects(
|
|||
val isKeyboardVisible by rememberIsKeyboardVisible()
|
||||
|
||||
LaunchedEffect(isKeyboardVisible) {
|
||||
if (isKeyboardVisible && alertConfig == null) {
|
||||
if (isKeyboardVisible && alertConfig == null && isSearchFieldFocused) {
|
||||
bottomSheetState.expand()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ markdownComposeView = "0.5.4"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "develop-775"
|
||||
tangemBlockchainSdk = "develop-782"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-381"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue