Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-18 10:44:43 +02:00
parent 4e28463b8a
commit 546bcc908f
24 changed files with 624 additions and 131 deletions

View file

@ -7,9 +7,12 @@ internal class DateTimeAdapter : JsonAdapter<DateTime>() {
@FromJson @FromJson
override fun fromJson(reader: JsonReader): DateTime? { override fun fromJson(reader: JsonReader): DateTime? {
val dateString = reader.nextString() ?: return null return if (reader.peek() == JsonReader.Token.NULL) {
reader.nextNull<DateTime>()
return DateTime.parse(dateString) } else {
val dateString = reader.nextString()
DateTime.parse(dateString)
}
} }
@ToJson @ToJson

View file

@ -10,9 +10,13 @@ internal class LocalDateAdapter : JsonAdapter<LocalDate>() {
@FromJson @FromJson
override fun fromJson(reader: JsonReader): LocalDate? { override fun fromJson(reader: JsonReader): LocalDate? {
return if (reader.peek() == JsonReader.Token.NULL) {
reader.nextNull<LocalDate>()
} else {
val dateString = reader.nextString() val dateString = reader.nextString()
return LocalDate.parse(dateString, formatter) return LocalDate.parse(dateString, formatter)
} }
}
@ToJson @ToJson
override fun toJson(writer: JsonWriter, value: LocalDate?) { override fun toJson(writer: JsonWriter, value: LocalDate?) {

View file

@ -2,6 +2,7 @@ package com.tangem.datasource.api.markets.models.response
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.joda.time.DateTime
import java.math.BigDecimal import java.math.BigDecimal
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@ -26,6 +27,8 @@ data class TokenMarketInfoResponse(
val insights: Insights?, val insights: Insights?,
@Json(name = "metrics") @Json(name = "metrics")
val metrics: Metrics?, val metrics: Metrics?,
@Json(name = "security_data")
val securityData: SecurityData?,
@Json(name = "links") @Json(name = "links")
val links: Links?, val links: Links?,
@Json(name = "price_performance") @Json(name = "price_performance")
@ -144,6 +147,28 @@ data class TokenMarketInfoResponse(
val allTime: Range?, val allTime: Range?,
) )
@JsonClass(generateAdapter = true)
data class SecurityData(
@Json(name = "total_security_score")
val totalSecurityScore: Float,
@Json(name = "provider_data")
val providerData: List<ProviderData>,
)
@JsonClass(generateAdapter = true)
data class ProviderData(
@Json(name = "provider_id")
val providerId: String,
@Json(name = "provider_name")
val providerName: String,
@Json(name = "link")
val link: String?,
@Json(name = "security_score")
val securityScore: Float,
@Json(name = "last_audit_date")
val lastAuditDate: DateTime?,
)
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Range( data class Range(
@Json(name = "low_price") @Json(name = "low_price")

View file

@ -568,7 +568,7 @@
<string name="onramp_no_available_providers">Keine verfügbaren Anbieter für diese Währung</string> <string name="onramp_no_available_providers">Keine verfügbaren Anbieter für diese Währung</string>
<string name="onramp_pay_with">Bezahlen mit</string> <string name="onramp_pay_with">Bezahlen mit</string>
<string name="onramp_redirecting_to_provider_subtitle">Du kannst Deine Transaktion beim Drittanbieter %s abschließen.</string> <string name="onramp_redirecting_to_provider_subtitle">Du kannst Deine Transaktion beim Drittanbieter %s abschließen.</string>
<string name="onramp_redirecting_to_provider_title">Umleitung auf %s...</string> <string name="onramp_redirecting_to_provider_title">Umleitung auf %s</string>
<string name="onramp_residency_bottomsheet_country_not_supported">Unsere Dienstleistungen sind in diesem Land nicht verfügbar</string> <string name="onramp_residency_bottomsheet_country_not_supported">Unsere Dienstleistungen sind in diesem Land nicht verfügbar</string>
<string name="onramp_residency_bottomsheet_country_subtitle">Änder oder bestätige bitte</string> <string name="onramp_residency_bottomsheet_country_subtitle">Änder oder bestätige bitte</string>
<string name="onramp_residency_bottomsheet_title">Dein Wohnsitz wurde identifiziert als</string> <string name="onramp_residency_bottomsheet_title">Dein Wohnsitz wurde identifiziert als</string>

View file

@ -180,6 +180,7 @@
<string name="common_terms_of_use">Condiciones de uso</string> <string name="common_terms_of_use">Condiciones de uso</string>
<string name="common_today">Hoy</string> <string name="common_today">Hoy</string>
<string name="common_transaction_failed">Transacción fallida</string> <string name="common_transaction_failed">Transacción fallida</string>
<string name="common_transaction_status">Estado de la transacción</string>
<string name="common_transactions">Transacciones</string> <string name="common_transactions">Transacciones</string>
<string name="common_transfer">Transferencia</string> <string name="common_transfer">Transferencia</string>
<string name="common_understand">Entiendo</string> <string name="common_understand">Entiendo</string>
@ -187,6 +188,7 @@
<string name="common_unreachable">Inaccesible</string> <string name="common_unreachable">Inaccesible</string>
<string name="common_unstake">Termine el staking</string> <string name="common_unstake">Termine el staking</string>
<string name="common_week">semana</string> <string name="common_week">semana</string>
<string name="common_with">con</string>
<string name="common_yes"></string> <string name="common_yes"></string>
<string name="contract_address_copied_message">Dirección del contract copiada</string> <string name="contract_address_copied_message">Dirección del contract copiada</string>
<string name="currency_subtitle_expanded">Redes disponibles</string> <string name="currency_subtitle_expanded">Redes disponibles</string>
@ -427,6 +429,7 @@
<string name="markets_token_details_fully_diluted_valuation_description">El valor teórico total de una criptomoneda si todas las monedas que podrían existir estuvieran en circulación, incluidas las que actualmente no circulan.</string> <string name="markets_token_details_fully_diluted_valuation_description">El valor teórico total de una criptomoneda si todas las monedas que podrían existir estuvieran en circulación, incluidas las que actualmente no circulan.</string>
<string name="markets_token_details_fully_diluted_valuation_full">Valoración totalmente diluida</string> <string name="markets_token_details_fully_diluted_valuation_full">Valoración totalmente diluida</string>
<string name="markets_token_details_genesis_date">Dato de Genesis</string> <string name="markets_token_details_genesis_date">Dato de Genesis</string>
<string name="markets_token_details_genesis_date_description">Vacío</string>
<string name="markets_token_details_high">Alto</string> <string name="markets_token_details_high">Alto</string>
<string name="markets_token_details_holders">Titulares</string> <string name="markets_token_details_holders">Titulares</string>
<string name="markets_token_details_holders_description">El cambio en el número de poseedores de tokens dentro de un período de tiempo específico</string> <string name="markets_token_details_holders_description">El cambio en el número de poseedores de tokens dentro de un período de tiempo específico</string>
@ -437,6 +440,7 @@
<string name="markets_token_details_liquidity_description">El cambio en la cantidad de liquidez disponible para el token durante el período de tiempo especificado</string> <string name="markets_token_details_liquidity_description">El cambio en la cantidad de liquidez disponible para el token durante el período de tiempo especificado</string>
<string name="markets_token_details_liquidity_full">Liquidez</string> <string name="markets_token_details_liquidity_full">Liquidez</string>
<string name="markets_token_details_liquidity_index">Índice de liquidez</string> <string name="markets_token_details_liquidity_index">Índice de liquidez</string>
<string name="markets_token_details_liquidity_index_description">Vacío</string>
<string name="markets_token_details_listed_on">Listado en</string> <string name="markets_token_details_listed_on">Listado en</string>
<string name="markets_token_details_low">Bajo</string> <string name="markets_token_details_low">Bajo</string>
<string name="markets_token_details_market_capitalization">Capital. de mercado</string> <string name="markets_token_details_market_capitalization">Capital. de mercado</string>
@ -453,6 +457,7 @@
<string name="markets_token_details_price_performance">Rendimiento de precios</string> <string name="markets_token_details_price_performance">Rendimiento de precios</string>
<string name="markets_token_details_repository">Repositorio</string> <string name="markets_token_details_repository">Repositorio</string>
<string name="markets_token_details_security_score">Puntuación de seguridad</string> <string name="markets_token_details_security_score">Puntuación de seguridad</string>
<string name="markets_token_details_security_score_description">La puntuación de seguridad de un token es una métrica que evalúa el nivel de seguridad de una blockchain o token en función de varios factores y se genera a partir de las fuentes que se enumeran a continuación.</string>
<string name="markets_token_details_social">Social</string> <string name="markets_token_details_social">Social</string>
<string name="markets_token_details_total_supply">Suministro total</string> <string name="markets_token_details_total_supply">Suministro total</string>
<string name="markets_token_details_total_supply_description">La cantidad máxima de monedas o tokens que pueden existir para una criptomoneda en particular</string> <string name="markets_token_details_total_supply_description">La cantidad máxima de monedas o tokens que pueden existir para una criptomoneda en particular</string>
@ -509,12 +514,12 @@
<string name="onboarding_navbar_title_creating_backup">Creando un backup</string> <string name="onboarding_navbar_title_creating_backup">Creando un backup</string>
<string name="onboarding_seed_button_read_more">Leer más sobre seed phrase</string> <string name="onboarding_seed_button_read_more">Leer más sobre seed phrase</string>
<plurals name="onboarding_seed_generate_message_words_count"> <plurals name="onboarding_seed_generate_message_words_count">
<item quantity="one"></item> <item quantity="one">Vacío</item>
<item quantity="other">Escriba estas %d palabras en el orden que se indica a continuación y guárdelas en un lugar seguro y secreto.</item> <item quantity="other">Escriba estas %d palabras en el orden que se indica a continuación y guárdelas en un lugar seguro y secreto.</item>
</plurals> </plurals>
<string name="onboarding_seed_generate_title">Su seed phrase</string> <string name="onboarding_seed_generate_title">Su seed phrase</string>
<plurals name="onboarding_seed_generate_words_count"> <plurals name="onboarding_seed_generate_words_count">
<item quantity="one"></item> <item quantity="one">Vacío</item>
<item quantity="other">%d palabras</item> <item quantity="other">%d palabras</item>
</plurals> </plurals>
<string name="onboarding_seed_import_message">Para importar su billetera, ingrese su seed phrase en el campo de abajo</string> <string name="onboarding_seed_import_message">Para importar su billetera, ingrese su seed phrase en el campo de abajo</string>
@ -566,12 +571,15 @@
<string name="onramp_min_amount_restriction">La cantidad a comprar debe ser como mínimo %s</string> <string name="onramp_min_amount_restriction">La cantidad a comprar debe ser como mínimo %s</string>
<string name="onramp_no_available_providers">No hay proveedores disponibles para esta moneda</string> <string name="onramp_no_available_providers">No hay proveedores disponibles para esta moneda</string>
<string name="onramp_pay_with">Pagar con</string> <string name="onramp_pay_with">Pagar con</string>
<string name="onramp_redirecting_to_provider_subtitle">Podrá completar su transacción en el proveedor externo, %s</string>
<string name="onramp_redirecting_to_provider_title">Redirigiendo a %s...</string>
<string name="onramp_residency_bottomsheet_country_not_supported">Nuestros servicios no están disponibles en este país</string> <string name="onramp_residency_bottomsheet_country_not_supported">Nuestros servicios no están disponibles en este país</string>
<string name="onramp_residency_bottomsheet_country_subtitle">Cámbielo o confírmelo</string> <string name="onramp_residency_bottomsheet_country_subtitle">Cámbielo o confírmelo</string>
<string name="onramp_residency_bottomsheet_title">Su residencia ha sido identificada como</string> <string name="onramp_residency_bottomsheet_title">Su residencia ha sido identificada como</string>
<string name="onramp_settings_residence">Residencia</string> <string name="onramp_settings_residence">Residencia</string>
<string name="onramp_settings_residence_description">Seleccione el país correcto para garantizar opciones de pago y servicios precisos.</string> <string name="onramp_settings_residence_description">Seleccione el país correcto para garantizar opciones de pago y servicios precisos.</string>
<string name="onramp_settings_title">Ajustes</string> <string name="onramp_settings_title">Ajustes</string>
<string name="onramp_transaction_status_footer_text">Puede comprobar el estado de la transacción desde la página del token</string>
<string name="onramp_via">Vía</string> <string name="onramp_via">Vía</string>
<string name="organize_tokens_group">Agrupar</string> <string name="organize_tokens_group">Agrupar</string>
<string name="organize_tokens_sort_by_balance">Por saldo</string> <string name="organize_tokens_sort_by_balance">Por saldo</string>
@ -804,7 +812,7 @@
<string name="staking_unbonding">Desunión</string> <string name="staking_unbonding">Desunión</string>
<string name="staking_unlocked_locked">Desbloquear</string> <string name="staking_unlocked_locked">Desbloquear</string>
<string name="staking_unlocking">Desbloqueando</string> <string name="staking_unlocking">Desbloqueando</string>
<string name="staking_unstake_amount_requirement_error">La cantidad para el staking debe ser al menos %s</string> <string name="staking_unstake_amount_requirement_error">La cantidad para el unstaking debe ser al menos %s</string>
<string name="staking_unstake_amount_validation_error">El monto excede el saldo apostado</string> <string name="staking_unstake_amount_validation_error">El monto excede el saldo apostado</string>
<string name="staking_unstaked">Sin staking</string> <string name="staking_unstaked">Sin staking</string>
<string name="staking_unstaking">Unstaking</string> <string name="staking_unstaking">Unstaking</string>
@ -884,6 +892,7 @@
<string name="transaction_history_transaction_from_address">desde: %s</string> <string name="transaction_history_transaction_from_address">desde: %s</string>
<string name="transaction_history_transaction_to_address">a: %s</string> <string name="transaction_history_transaction_to_address">a: %s</string>
<string name="transaction_history_transaction_validator">validador: %s</string> <string name="transaction_history_transaction_validator">validador: %s</string>
<string name="transfer_min_amount_error">Mínimo %s</string>
<string name="transfer_notification_invalid_minimum_transaction_amount_text">El monto mínimo para realizar esta transacción es %1$s.</string> <string name="transfer_notification_invalid_minimum_transaction_amount_text">El monto mínimo para realizar esta transacción es %1$s.</string>
<string name="try_to_load_data_again_button_title">Inténtelo de nuevo</string> <string name="try_to_load_data_again_button_title">Inténtelo de nuevo</string>
<string name="twin_error_same_card">Ha escaneado la misma tarjeta. Para crear una billetera gemela, necesite escanear la tarjeta con no. %d</string> <string name="twin_error_same_card">Ha escaneado la misma tarjeta. Para crear una billetera gemela, necesite escanear la tarjeta con no. %d</string>
@ -949,6 +958,9 @@
<string name="wallet_network_group_title">%s red</string> <string name="wallet_network_group_title">%s red</string>
<string name="wallet_notification_address_copied">La dirección se copió correctamente</string> <string name="wallet_notification_address_copied">La dirección se copió correctamente</string>
<string name="wallet_notification_no_internet">Sin conexión a internet</string> <string name="wallet_notification_no_internet">Sin conexión a internet</string>
<string name="wallet_promo_banner_button_title">Consíguelo ahora con un 10 %% de descuento</string>
<string name="wallet_promo_banner_description">Accede a más de 13 000 criptomonedas. Compra, vende, intercambia y realiza staking con un solo toque.\nVincula hasta tres tarjetas para hacer copias de seguridad.</string>
<string name="wallet_promo_banner_title">Descubre Tangem Wallet</string>
<string name="wallet_settings_title">Ajusta de la wallet</string> <string name="wallet_settings_title">Ajusta de la wallet</string>
<string name="wallet_title">Tangem</string> <string name="wallet_title">Tangem</string>
<string name="warning_access_denied_message">Use %s o escanee una tarjeta/anillo para desbloquear el acceso a su billetera</string> <string name="warning_access_denied_message">Use %s o escanee una tarjeta/anillo para desbloquear el acceso a su billetera</string>

View file

@ -884,6 +884,7 @@
<string name="transaction_history_transaction_from_address">de : %s</string> <string name="transaction_history_transaction_from_address">de : %s</string>
<string name="transaction_history_transaction_to_address">à : %s</string> <string name="transaction_history_transaction_to_address">à : %s</string>
<string name="transaction_history_transaction_validator">validateur : %s</string> <string name="transaction_history_transaction_validator">validateur : %s</string>
<string name="transfer_min_amount_error">Minimum %s</string>
<string name="transfer_notification_invalid_minimum_transaction_amount_text">Le montant minimum pour effectuer cette transaction est %1$s.</string> <string name="transfer_notification_invalid_minimum_transaction_amount_text">Le montant minimum pour effectuer cette transaction est %1$s.</string>
<string name="try_to_load_data_again_button_title">Réessayez</string> <string name="try_to_load_data_again_button_title">Réessayez</string>
<string name="twin_error_same_card">Vous avez scanné la même carte. Pour créer un portefeuille jumeau, vous devez scanner la carte portant le numéro %d</string> <string name="twin_error_same_card">Vous avez scanné la même carte. Pour créer un portefeuille jumeau, vous devez scanner la carte portant le numéro %d</string>

View file

@ -177,6 +177,7 @@
<string name="common_terms_of_use">利用規約</string> <string name="common_terms_of_use">利用規約</string>
<string name="common_today">今日</string> <string name="common_today">今日</string>
<string name="common_transaction_failed">取引が失敗しました</string> <string name="common_transaction_failed">取引が失敗しました</string>
<string name="common_transaction_status">取引状況</string>
<string name="common_transactions">取引</string> <string name="common_transactions">取引</string>
<string name="common_transfer">送金</string> <string name="common_transfer">送金</string>
<string name="common_understand">わかりました</string> <string name="common_understand">わかりました</string>
@ -184,6 +185,7 @@
<string name="common_unreachable">アクセスできません</string> <string name="common_unreachable">アクセスできません</string>
<string name="common_unstake">ステーキング解除</string> <string name="common_unstake">ステーキング解除</string>
<string name="common_week"></string> <string name="common_week"></string>
<string name="common_with"></string>
<string name="common_yes">はい</string> <string name="common_yes">はい</string>
<string name="contract_address_copied_message">コントラクトアドレスをコピーしました!</string> <string name="contract_address_copied_message">コントラクトアドレスをコピーしました!</string>
<string name="currency_subtitle_expanded">利用可能なネットワーク</string> <string name="currency_subtitle_expanded">利用可能なネットワーク</string>
@ -567,6 +569,7 @@
<string name="onramp_settings_residence">住居</string> <string name="onramp_settings_residence">住居</string>
<string name="onramp_settings_residence_description">正確なお支払い方法とサービスを確保するため、正しい国を選択してください。</string> <string name="onramp_settings_residence_description">正確なお支払い方法とサービスを確保するため、正しい国を選択してください。</string>
<string name="onramp_settings_title">設定</string> <string name="onramp_settings_title">設定</string>
<string name="onramp_transaction_status_footer_text">トークンページから取引状況を確認できます</string>
<string name="onramp_via">経由</string> <string name="onramp_via">経由</string>
<string name="organize_tokens_group">グループ</string> <string name="organize_tokens_group">グループ</string>
<string name="organize_tokens_sort_by_balance">残高順</string> <string name="organize_tokens_sort_by_balance">残高順</string>
@ -943,6 +946,9 @@
<string name="wallet_network_group_title">%sネットワーク</string> <string name="wallet_network_group_title">%sネットワーク</string>
<string name="wallet_notification_address_copied">アドレスがクリップボードにコピーされました</string> <string name="wallet_notification_address_copied">アドレスがクリップボードにコピーされました</string>
<string name="wallet_notification_no_internet">インターネット接続がありません</string> <string name="wallet_notification_no_internet">インターネット接続がありません</string>
<string name="wallet_promo_banner_button_title">今すぐ10 %オフで購入</string>
<string name="wallet_promo_banner_description">1.3万種類以上の暗号資産にアクセス。ワンタップで買付、売却、スワップ、ステーキングが可能です。\nバックアップ用に最大3枚のカードを連携できます。</string>
<string name="wallet_promo_banner_title">Tangemウォレットを見る</string>
<string name="wallet_settings_title">ウォレット設定</string> <string name="wallet_settings_title">ウォレット設定</string>
<string name="wallet_title">Tangem</string> <string name="wallet_title">Tangem</string>
<string name="warning_access_denied_message">%sを使用するか、カード / リングをスキャンしてウォレットにアクセスしてください</string> <string name="warning_access_denied_message">%sを使用するか、カード / リングをスキャンしてウォレットにアクセスしてください</string>

View file

@ -183,6 +183,7 @@
<string name="common_terms_of_use">Условиями использования</string> <string name="common_terms_of_use">Условиями использования</string>
<string name="common_today">Сегодня</string> <string name="common_today">Сегодня</string>
<string name="common_transaction_failed">Ошибка транзакции</string> <string name="common_transaction_failed">Ошибка транзакции</string>
<string name="common_transaction_status">Статус транзакции</string>
<string name="common_transactions">Транзакции</string> <string name="common_transactions">Транзакции</string>
<string name="common_transfer">Перевод</string> <string name="common_transfer">Перевод</string>
<string name="common_understand">Я понял</string> <string name="common_understand">Я понял</string>
@ -190,6 +191,7 @@
<string name="common_unreachable">Недоступно</string> <string name="common_unreachable">Недоступно</string>
<string name="common_unstake">Завершить стейкинг</string> <string name="common_unstake">Завершить стейкинг</string>
<string name="common_week">неделю</string> <string name="common_week">неделю</string>
<string name="common_with">с</string>
<string name="common_yes">Да</string> <string name="common_yes">Да</string>
<string name="contract_address_copied_message">Адрес контракта скопирован!</string> <string name="contract_address_copied_message">Адрес контракта скопирован!</string>
<string name="currency_subtitle_expanded">Доступные сети</string> <string name="currency_subtitle_expanded">Доступные сети</string>
@ -576,7 +578,10 @@
<string name="onramp_currency_search">Поиск по валюте</string> <string name="onramp_currency_search">Поиск по валюте</string>
<string name="onramp_max_amount_restriction">Сумма покупки не может быть больше, чем %s</string> <string name="onramp_max_amount_restriction">Сумма покупки не может быть больше, чем %s</string>
<string name="onramp_min_amount_restriction">Сумма покупки должна составлять минимум %s</string> <string name="onramp_min_amount_restriction">Сумма покупки должна составлять минимум %s</string>
<string name="onramp_no_available_providers">Для данной валюты нет доступных провайдеров</string>
<string name="onramp_pay_with">Оплата с</string> <string name="onramp_pay_with">Оплата с</string>
<string name="onramp_redirecting_to_provider_subtitle">Вы сможете завершить транзакцию через сервис стороннего провайдера, %s</string>
<string name="onramp_redirecting_to_provider_title">Переход к %s ...</string>
<string name="onramp_residency_bottomsheet_country_not_supported">Наши сервисы недоступны в данной стране</string> <string name="onramp_residency_bottomsheet_country_not_supported">Наши сервисы недоступны в данной стране</string>
<string name="onramp_settings_title">Настройки</string> <string name="onramp_settings_title">Настройки</string>
<string name="onramp_via">Через</string> <string name="onramp_via">Через</string>
@ -860,14 +865,14 @@
<string name="toast_balances_shown">Балансы показаны</string> <string name="toast_balances_shown">Балансы показаны</string>
<string name="toast_undo">Отменить</string> <string name="toast_undo">Отменить</string>
<string name="token_button_unavailability_generic_description">Выбранная операция в данный момент недоступна. Попробуйте позже.</string> <string name="token_button_unavailability_generic_description">Выбранная операция в данный момент недоступна. Попробуйте позже.</string>
<string name="token_button_unavailability_reason_buy_unavailable">В данный момент покупка монеты %s недоступна. Следите за нашими обновлениями.</string> <string name="token_button_unavailability_reason_buy_unavailable">Покупка %s в данный момент не поддерживается ни одним провайдером. Мы работаем над добавлением новых возможностей. Следите за нашими новостями.</string>
<string name="token_button_unavailability_reason_empty_balance_sell">У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства.</string> <string name="token_button_unavailability_reason_empty_balance_sell">У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства.</string>
<string name="token_button_unavailability_reason_empty_balance_send">У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства.</string> <string name="token_button_unavailability_reason_empty_balance_send">У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства.</string>
<string name="token_button_unavailability_reason_not_exchangeable">В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями.</string> <string name="token_button_unavailability_reason_not_exchangeable">Обмен %s в данный момент не поддерживается ни одним провайдером. Мы работаем над добавлением новых возможностей. Следите за нашими новостями.</string>
<string name="token_button_unavailability_reason_pending_transaction_sell">Продажа средств станет доступной после завершения транзакции(-ий) в сети %s</string> <string name="token_button_unavailability_reason_pending_transaction_sell">Продажа средств станет доступной после завершения транзакции(-ий) в сети %s</string>
<string name="token_button_unavailability_reason_pending_transaction_send">Отправка средств станет доступной после завершения транзакции(-ий) в сети %s</string> <string name="token_button_unavailability_reason_pending_transaction_send">Отправка средств станет доступной после завершения транзакции(-ий) в сети %s</string>
<string name="token_button_unavailability_reason_sell_unavailable">В данный момент продажа %s недоступна. Следите за нашими обновлениями.</string> <string name="token_button_unavailability_reason_sell_unavailable">Продажа %s в данный момент не поддерживается ни одним провайдером. Мы работаем над добавлением новых возможностей. Следите за нашими новостями.</string>
<string name="token_button_unavailability_reason_staking_unavailable">В данный момент стейкинг монеты %s недоступен. Следите за нашими обновлениями.</string> <string name="token_button_unavailability_reason_staking_unavailable">Стейкинг %s в данный момент не поддерживается ни одним провайдером. Мы работаем над добавлением новых возможностей. Следите за нашими новостями.</string>
<string name="token_details_generate_xpub">Сгенерировать XPUB</string> <string name="token_details_generate_xpub">Сгенерировать XPUB</string>
<string name="token_details_hide_alert_hide">Скрыть</string> <string name="token_details_hide_alert_hide">Скрыть</string>
<string name="token_details_hide_alert_message">Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами.</string> <string name="token_details_hide_alert_message">Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами.</string>
@ -893,6 +898,7 @@
<string name="transaction_history_transaction_from_address">от: %s</string> <string name="transaction_history_transaction_from_address">от: %s</string>
<string name="transaction_history_transaction_to_address">на: %s</string> <string name="transaction_history_transaction_to_address">на: %s</string>
<string name="transaction_history_transaction_validator">валидатор: %s</string> <string name="transaction_history_transaction_validator">валидатор: %s</string>
<string name="transfer_min_amount_error">Минимум %s</string>
<string name="transfer_notification_invalid_minimum_transaction_amount_text">Минимальная сумма транзакции равна %1$s.</string> <string name="transfer_notification_invalid_minimum_transaction_amount_text">Минимальная сумма транзакции равна %1$s.</string>
<string name="try_to_load_data_again_button_title">Попробовать снова</string> <string name="try_to_load_data_again_button_title">Попробовать снова</string>
<string name="twin_error_same_card">Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d</string> <string name="twin_error_same_card">Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d</string>
@ -958,6 +964,9 @@
<string name="wallet_network_group_title">Сеть %s</string> <string name="wallet_network_group_title">Сеть %s</string>
<string name="wallet_notification_address_copied">Адрес скопирован в буфер обмена</string> <string name="wallet_notification_address_copied">Адрес скопирован в буфер обмена</string>
<string name="wallet_notification_no_internet">Нет соединения с интернетом</string> <string name="wallet_notification_no_internet">Нет соединения с интернетом</string>
<string name="wallet_promo_banner_button_title">Получить с 10%% скидкой</string>
<string name="wallet_promo_banner_description">Получите доступ к более чем 13 000 криптовалют. Покупайте, продавайте, обменяйте и стейкайте в один клик. Свяжите до трех карт для резервного копирования. </string>
<string name="wallet_promo_banner_title">Откройте Tangem Wallet </string>
<string name="wallet_settings_title">Настройки кошелька</string> <string name="wallet_settings_title">Настройки кошелька</string>
<string name="wallet_title">Tangem</string> <string name="wallet_title">Tangem</string>
<string name="warning_access_denied_message">Используйте %s или отсканируйте карту/кольцо, чтобы разблокировать доступ к вашему кошельку</string> <string name="warning_access_denied_message">Используйте %s или отсканируйте карту/кольцо, чтобы разблокировать доступ к вашему кошельку</string>

View file

@ -186,6 +186,7 @@
<string name="common_terms_of_use">Умовами використання</string> <string name="common_terms_of_use">Умовами використання</string>
<string name="common_today">Сьогодні</string> <string name="common_today">Сьогодні</string>
<string name="common_transaction_failed">Помилка транзакції</string> <string name="common_transaction_failed">Помилка транзакції</string>
<string name="common_transaction_status">Статус транзакції</string>
<string name="common_transactions">Транзакції</string> <string name="common_transactions">Транзакції</string>
<string name="common_transfer">Переказ</string> <string name="common_transfer">Переказ</string>
<string name="common_understand">Я зрозумів</string> <string name="common_understand">Я зрозумів</string>
@ -193,6 +194,7 @@
<string name="common_unreachable">Недоступно</string> <string name="common_unreachable">Недоступно</string>
<string name="common_unstake">Скасувати стейкінг</string> <string name="common_unstake">Скасувати стейкінг</string>
<string name="common_week">тиждень</string> <string name="common_week">тиждень</string>
<string name="common_with">з</string>
<string name="common_yes">Так</string> <string name="common_yes">Так</string>
<string name="contract_address_copied_message">Адреса контракту скопійована!</string> <string name="contract_address_copied_message">Адреса контракту скопійована!</string>
<string name="currency_subtitle_expanded">Доступні мережі</string> <string name="currency_subtitle_expanded">Доступні мережі</string>
@ -465,6 +467,7 @@
<string name="markets_token_details_price_performance">Цінова ефективність</string> <string name="markets_token_details_price_performance">Цінова ефективність</string>
<string name="markets_token_details_repository">Репозиторій</string> <string name="markets_token_details_repository">Репозиторій</string>
<string name="markets_token_details_security_score">Оцінка безпеки</string> <string name="markets_token_details_security_score">Оцінка безпеки</string>
<string name="markets_token_details_security_score_description">Оцінка безпеки токена це метрика, яка оцінює рівень безпеки блокчейну або токена на основі різних факторів і складається з джерел, перелічених нижче.</string>
<string name="markets_token_details_social">Соцмережі</string> <string name="markets_token_details_social">Соцмережі</string>
<string name="markets_token_details_total_supply">Загальна проп.</string> <string name="markets_token_details_total_supply">Загальна проп.</string>
<string name="markets_token_details_total_supply_description">Максимальна кількість монет або токенів, яка може коли-небудь існувати для певної криптовалюти</string> <string name="markets_token_details_total_supply_description">Максимальна кількість монет або токенів, яка може коли-небудь існувати для певної криптовалюти</string>
@ -521,7 +524,7 @@
<string name="onboarding_navbar_title_creating_backup">Резервна копія</string> <string name="onboarding_navbar_title_creating_backup">Резервна копія</string>
<string name="onboarding_seed_button_read_more">Дізнатися більше про seed-фразу</string> <string name="onboarding_seed_button_read_more">Дізнатися більше про seed-фразу</string>
<plurals name="onboarding_seed_generate_message_words_count"> <plurals name="onboarding_seed_generate_message_words_count">
<item quantity="one">Запишіть це %d слово у порядку, вказаному нижче, та збережіть його у надійному місці.</item> <item quantity="one"></item>
<item quantity="few">Запишіть ці %d слова у порядку, вказаному нижче, та збережіть їх у надійному місці.</item> <item quantity="few">Запишіть ці %d слова у порядку, вказаному нижче, та збережіть їх у надійному місці.</item>
<item quantity="many">Запишіть ці %d слів у порядку, вказаному нижче, та збережіть їх у надійному місці.</item> <item quantity="many">Запишіть ці %d слів у порядку, вказаному нижче, та збережіть їх у надійному місці.</item>
<item quantity="other">Запишіть ці %d слів у порядку, вказаному нижче, та збережіть їх у надійному місці.</item> <item quantity="other">Запишіть ці %d слів у порядку, вказаному нижче, та збережіть їх у надійному місці.</item>
@ -578,12 +581,18 @@
<string name="onramp_country_search">Пошук за країною</string> <string name="onramp_country_search">Пошук за країною</string>
<string name="onramp_country_unavailable">Недоступно</string> <string name="onramp_country_unavailable">Недоступно</string>
<string name="onramp_currency_search">Пошук по валюті</string> <string name="onramp_currency_search">Пошук по валюті</string>
<string name="onramp_max_amount_restriction">Сума покупки не може бути більше ніж %s</string>
<string name="onramp_min_amount_restriction">Сума покупки повинна бути не менше %s</string>
<string name="onramp_no_available_providers">Для данної валюти немає доступних провайдерів</string>
<string name="onramp_pay_with">Оплата з</string> <string name="onramp_pay_with">Оплата з</string>
<string name="onramp_redirecting_to_provider_subtitle">Ви зможете завершити транзакцію у стороннього постачальника, %s</string>
<string name="onramp_redirecting_to_provider_title">Перенаправлення на %s...</string>
<string name="onramp_residency_bottomsheet_country_not_supported">Наші сервіси недоступні в цій країні</string> <string name="onramp_residency_bottomsheet_country_not_supported">Наші сервіси недоступні в цій країні</string>
<string name="onramp_residency_bottomsheet_country_subtitle">Змінити або підтвердити</string> <string name="onramp_residency_bottomsheet_country_subtitle">Змінити або підтвердити</string>
<string name="onramp_residency_bottomsheet_title">Ваше місце проживання визначено як</string> <string name="onramp_residency_bottomsheet_title">Ваше місце проживання визначено як</string>
<string name="onramp_settings_residence_description">Будь ласка, виберіть правильну країну, щоб забезпечити точні способи оплати та послуги.</string> <string name="onramp_settings_residence_description">Будь ласка, виберіть правильну країну, щоб забезпечити точні способи оплати та послуги.</string>
<string name="onramp_settings_title">Параметри</string> <string name="onramp_settings_title">Параметри</string>
<string name="onramp_transaction_status_footer_text">Ви можете перевірити статус транзакції на сторінці токена</string>
<string name="onramp_via">Через</string> <string name="onramp_via">Через</string>
<string name="organize_tokens_group">Групами</string> <string name="organize_tokens_group">Групами</string>
<string name="organize_tokens_sort_by_balance">За балансом</string> <string name="organize_tokens_sort_by_balance">За балансом</string>
@ -820,6 +829,7 @@
<string name="staking_unbonding">Розблокування</string> <string name="staking_unbonding">Розблокування</string>
<string name="staking_unlocked_locked">Розблокувати</string> <string name="staking_unlocked_locked">Розблокувати</string>
<string name="staking_unlocking">Розблокування</string> <string name="staking_unlocking">Розблокування</string>
<string name="staking_unstake_amount_requirement_error">Сума для виведення зі стейкінгу повинна бути не менше %s</string>
<string name="staking_unstake_amount_validation_error">Сума перевищує баланс стейкінгу</string> <string name="staking_unstake_amount_validation_error">Сума перевищує баланс стейкінгу</string>
<string name="staking_unstaked">Вивід зі стейкінгу</string> <string name="staking_unstaked">Вивід зі стейкінгу</string>
<string name="staking_unstaking">Зняти зі стейкінгу</string> <string name="staking_unstaking">Зняти зі стейкінгу</string>
@ -849,7 +859,7 @@
<string name="swapping_alert_cex_description_with_slippage">У суму входить:\n- комісія провайдера\n- мережева комісія за відправку %1$s з біржі назад на адресу користувача. \n\nПроскакування провайдера становить до %2$s</string> <string name="swapping_alert_cex_description_with_slippage">У суму входить:\n- комісія провайдера\n- мережева комісія за відправку %1$s з біржі назад на адресу користувача. \n\nПроскакування провайдера становить до %2$s</string>
<string name="swapping_alert_dex_description">Сума включає комісію постачальника послуг.</string> <string name="swapping_alert_dex_description">Сума включає комісію постачальника послуг.</string>
<string name="swapping_alert_dex_description_with_slippage">Сума включає комісію провайдера послуг. \n\nПроскакування провайдера до %s</string> <string name="swapping_alert_dex_description_with_slippage">Сума включає комісію провайдера послуг. \n\nПроскакування провайдера до %s</string>
<string name="swapping_alert_title">Комісії</string> <string name="swapping_alert_title">Інформація</string>
<string name="swapping_approve_information_text">Всі децентралізовані біржі вимагають схвалення, щоб запобігти доступу смарт-контрактів до вашого гаманця без вашого дозволу. За задумом смарт-контракти не можуть отримати доступ до ваших токенів без вашого схвалення. \"Розблоковуючи\" свої токени, ви дозволяєте смарт-контракту 1inch витрачати ваші активи. Майнери мережі отримують плату за газ (сплачену вами), щоб зафіксувати цю дію в блокчейні. Ви можете обміняти свій токен після того, як дасте дозвіл.</string> <string name="swapping_approve_information_text">Всі децентралізовані біржі вимагають схвалення, щоб запобігти доступу смарт-контрактів до вашого гаманця без вашого дозволу. За задумом смарт-контракти не можуть отримати доступ до ваших токенів без вашого схвалення. \"Розблоковуючи\" свої токени, ви дозволяєте смарт-контракту 1inch витрачати ваші активи. Майнери мережі отримують плату за газ (сплачену вами), щоб зафіксувати цю дію в блокчейні. Ви можете обміняти свій токен після того, як дасте дозвіл.</string>
<string name="swapping_approve_information_title">Підтвердити</string> <string name="swapping_approve_information_title">Підтвердити</string>
<string name="swapping_fee_estimation_error_text">Помилка при розрахунку комісії. Будь ласка, надішліть відгук до служби підтримки.</string> <string name="swapping_fee_estimation_error_text">Помилка при розрахунку комісії. Будь ласка, надішліть відгук до служби підтримки.</string>
@ -899,6 +909,7 @@
<string name="transaction_history_transaction_from_address">від: %s</string> <string name="transaction_history_transaction_from_address">від: %s</string>
<string name="transaction_history_transaction_to_address">до: %s</string> <string name="transaction_history_transaction_to_address">до: %s</string>
<string name="transaction_history_transaction_validator">валідатор: %s</string> <string name="transaction_history_transaction_validator">валідатор: %s</string>
<string name="transfer_min_amount_error">Мінімум %s</string>
<string name="transfer_notification_invalid_minimum_transaction_amount_text">Мінімальна сума транзакції становить %1$s.</string> <string name="transfer_notification_invalid_minimum_transaction_amount_text">Мінімальна сума транзакції становить %1$s.</string>
<string name="try_to_load_data_again_button_title">Спробуйте знову</string> <string name="try_to_load_data_again_button_title">Спробуйте знову</string>
<string name="twin_error_same_card">Ви відсканували одну й ту саму картку. Для створення twin-гаманця вам потрібно відсканувати картку з номером %d</string> <string name="twin_error_same_card">Ви відсканували одну й ту саму картку. Для створення twin-гаманця вам потрібно відсканувати картку з номером %d</string>

View file

@ -180,6 +180,7 @@
<string name="common_terms_of_use">Terms of Use</string> <string name="common_terms_of_use">Terms of Use</string>
<string name="common_today">Today</string> <string name="common_today">Today</string>
<string name="common_transaction_failed">Transaction failed</string> <string name="common_transaction_failed">Transaction failed</string>
<string name="common_transaction_status">Transaction status</string>
<string name="common_transactions">Transactions</string> <string name="common_transactions">Transactions</string>
<string name="common_transfer">Transfer</string> <string name="common_transfer">Transfer</string>
<string name="common_understand">I understand</string> <string name="common_understand">I understand</string>
@ -187,6 +188,7 @@
<string name="common_unreachable">Unreachable</string> <string name="common_unreachable">Unreachable</string>
<string name="common_unstake">Unstake</string> <string name="common_unstake">Unstake</string>
<string name="common_week">week</string> <string name="common_week">week</string>
<string name="common_with">with</string>
<string name="common_yes">Yes</string> <string name="common_yes">Yes</string>
<string name="contract_address_copied_message">Contract address copied!</string> <string name="contract_address_copied_message">Contract address copied!</string>
<string name="currency_subtitle_expanded">Available networks</string> <string name="currency_subtitle_expanded">Available networks</string>
@ -575,6 +577,7 @@
<string name="onramp_settings_residence">Residence</string> <string name="onramp_settings_residence">Residence</string>
<string name="onramp_settings_residence_description">Please select the correct country to ensure accurate payment options and services.</string> <string name="onramp_settings_residence_description">Please select the correct country to ensure accurate payment options and services.</string>
<string name="onramp_settings_title">Settings</string> <string name="onramp_settings_title">Settings</string>
<string name="onramp_transaction_status_footer_text">You can check the transaction status from the token page</string>
<string name="onramp_via">Via</string> <string name="onramp_via">Via</string>
<string name="organize_tokens_group">Group</string> <string name="organize_tokens_group">Group</string>
<string name="organize_tokens_sort_by_balance">By balance</string> <string name="organize_tokens_sort_by_balance">By balance</string>
@ -853,14 +856,14 @@
<string name="toast_balances_shown">Balances shown</string> <string name="toast_balances_shown">Balances shown</string>
<string name="toast_undo">Undo</string> <string name="toast_undo">Undo</string>
<string name="token_button_unavailability_generic_description">This operation is currently unavailable. Please try again later.</string> <string name="token_button_unavailability_generic_description">This operation is currently unavailable. Please try again later.</string>
<string name="token_button_unavailability_reason_buy_unavailable">Buying %s is not available at the moment. Please check our updates.</string> <string name="token_button_unavailability_reason_buy_unavailable">Buying %s is not supported by current providers, but we are working to add more options.</string>
<string name="token_button_unavailability_reason_empty_balance_sell">You do not have funds to sell. Top up your account to be able to sell funds from it.</string> <string name="token_button_unavailability_reason_empty_balance_sell">You do not have funds to sell. Top up your account to be able to sell funds from it.</string>
<string name="token_button_unavailability_reason_empty_balance_send">You do not have funds to send. Top up your account to be able to send funds from it.</string> <string name="token_button_unavailability_reason_empty_balance_send">You do not have funds to send. Top up your account to be able to send funds from it.</string>
<string name="token_button_unavailability_reason_not_exchangeable">Swapping %s is not available at the moment. Please check our updates.</string> <string name="token_button_unavailability_reason_not_exchangeable">Swapping %s is not supported by current providers, but we are working to add more options.</string>
<string name="token_button_unavailability_reason_pending_transaction_sell">Selling funds will be available once the pending transaction(s) in network %s is complete</string> <string name="token_button_unavailability_reason_pending_transaction_sell">Selling funds will be available once the pending transaction(s) in network %s is complete</string>
<string name="token_button_unavailability_reason_pending_transaction_send">Sending funds will be available once the pending transaction(s) in network %s is complete</string> <string name="token_button_unavailability_reason_pending_transaction_send">Sending funds will be available once the pending transaction(s) in network %s is complete</string>
<string name="token_button_unavailability_reason_sell_unavailable">Selling %s is not available at the moment. Please check our updates.</string> <string name="token_button_unavailability_reason_sell_unavailable">Selling %s is not supported by current providers, but we are working to add more options.</string>
<string name="token_button_unavailability_reason_staking_unavailable">Staking %s is not available at the moment. Please check our updates.</string> <string name="token_button_unavailability_reason_staking_unavailable">Staking %s is not supported by current providers, but we are working to add more options.</string>
<string name="token_details_generate_xpub">Generate XPUB</string> <string name="token_details_generate_xpub">Generate XPUB</string>
<string name="token_details_hide_alert_hide">Hide</string> <string name="token_details_hide_alert_hide">Hide</string>
<string name="token_details_hide_alert_message">You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.</string> <string name="token_details_hide_alert_message">You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.</string>
@ -953,6 +956,9 @@
<string name="wallet_network_group_title">%s network</string> <string name="wallet_network_group_title">%s network</string>
<string name="wallet_notification_address_copied">Address was copied to clipboard</string> <string name="wallet_notification_address_copied">Address was copied to clipboard</string>
<string name="wallet_notification_no_internet">No internet connection</string> <string name="wallet_notification_no_internet">No internet connection</string>
<string name="wallet_promo_banner_button_title">Get now with 10%% off</string>
<string name="wallet_promo_banner_description">Access 13,000+ cryptocurrencies. Buy, sell, swap, and stake with a single tap.\nLink up to three cards for a backup.</string>
<string name="wallet_promo_banner_title">Discover Tangem Wallet</string>
<string name="wallet_settings_title">Wallet settings</string> <string name="wallet_settings_title">Wallet settings</string>
<string name="wallet_title">Tangem</string> <string name="wallet_title">Tangem</string>
<string name="warning_access_denied_message">Use %s or scan a card/ring to unlock access to your wallet</string> <string name="warning_access_denied_message">Use %s or scan a card/ring to unlock access to your wallet</string>

View file

@ -36,6 +36,7 @@ data class TangemDimens internal constructor(
val radius26: Dp = 26.dp, val radius26: Dp = 26.dp,
val radius28: Dp = 28.dp, val radius28: Dp = 28.dp,
val radius36: Dp = 36.dp, val radius36: Dp = 36.dp,
val radius40: Dp = 40.dp,
// endregion Radius // endregion Radius
// region Size // region Size
val size0: Dp = 0.dp, val size0: Dp = 0.dp,

View file

@ -1,9 +1,11 @@
package com.tangem.data.markets.converters package com.tangem.data.markets.converters
import android.net.Uri
import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.isSupportedInApp import com.tangem.blockchainsdk.utils.isSupportedInApp
import com.tangem.datasource.api.markets.models.response.TokenMarketInfoResponse import com.tangem.datasource.api.markets.models.response.TokenMarketInfoResponse
import com.tangem.domain.markets.BuildConfig
import com.tangem.domain.markets.TokenMarketInfo import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.markets.TokenQuotes import com.tangem.domain.markets.TokenQuotes
import com.tangem.utils.converter.Converter import com.tangem.utils.converter.Converter
@ -11,6 +13,9 @@ import java.math.BigDecimal
internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, TokenMarketInfo> { internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, TokenMarketInfo> {
private const val PROD_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api/security_provider/"
private const val DEV_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api.dev/security_provider/"
override fun convert(value: TokenMarketInfoResponse): TokenMarketInfo { override fun convert(value: TokenMarketInfoResponse): TokenMarketInfo {
return with(value) { return with(value) {
TokenMarketInfo( TokenMarketInfo(
@ -23,6 +28,7 @@ internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, To
fullDescription = fullDescription, fullDescription = fullDescription,
insights = insights?.convert(), insights = insights?.convert(),
metrics = metrics?.convert(), metrics = metrics?.convert(),
securityData = securityData?.convert(),
links = links?.convert(), links = links?.convert(),
pricePerformance = pricePerformance?.convert(), pricePerformance = pricePerformance?.convert(),
exchangesAmount = exchangesAmount, exchangesAmount = exchangesAmount,
@ -105,6 +111,31 @@ internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, To
) )
} }
private fun TokenMarketInfoResponse.SecurityData.convert(): TokenMarketInfo.SecurityData {
return TokenMarketInfo.SecurityData(
totalSecurityScore = totalSecurityScore,
providerData = providerData.map { it.convert() },
)
}
private fun TokenMarketInfoResponse.ProviderData.convert(): TokenMarketInfo.ProviderData {
return TokenMarketInfo.ProviderData(
providerId = providerId,
providerName = providerName,
urlData = link?.convertToUrlData(),
securityScore = securityScore,
lastAuditDate = lastAuditDate,
iconUrl = "${getImageHost()}large/$providerId.png",
)
}
private fun String.convertToUrlData(): TokenMarketInfo.ProviderUrlData {
return TokenMarketInfo.ProviderUrlData(
fullUrl = this,
rootHost = this.extractMainDomainOrNull(),
)
}
private fun TokenMarketInfoResponse.Links.convert(): TokenMarketInfo.Links { private fun TokenMarketInfoResponse.Links.convert(): TokenMarketInfo.Links {
return TokenMarketInfo.Links( return TokenMarketInfo.Links(
officialLinks = officialLinks?.convert(), officialLinks = officialLinks?.convert(),
@ -139,4 +170,27 @@ internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, To
high = high, high = high,
) )
} }
private fun String.extractMainDomainOrNull(): String? {
return runCatching {
val uri = Uri.parse(this)
val host = uri.host ?: return null
val parts = host.split(".")
if (parts.size >= 2) {
parts.takeLast(2).joinToString(".")
} else {
host
}
}.getOrNull()
}
private fun getImageHost(): String {
return if (BuildConfig.TESTER_MENU_ENABLED) {
DEV_IMAGE_HOST
} else {
PROD_IMAGE_HOST
}
}
} }

View file

@ -1,5 +1,6 @@
package com.tangem.domain.markets package com.tangem.domain.markets
import org.joda.time.DateTime
import java.math.BigDecimal import java.math.BigDecimal
data class TokenMarketInfo( data class TokenMarketInfo(
@ -12,6 +13,7 @@ data class TokenMarketInfo(
val fullDescription: String?, val fullDescription: String?,
val insights: Insights?, val insights: Insights?,
val metrics: Metrics?, val metrics: Metrics?,
val securityData: SecurityData?,
val links: Links?, val links: Links?,
val pricePerformance: PricePerformance?, val pricePerformance: PricePerformance?,
val exchangesAmount: Int?, val exchangesAmount: Int?,
@ -51,6 +53,25 @@ data class TokenMarketInfo(
val fullyDilutedValuation: BigDecimal?, val fullyDilutedValuation: BigDecimal?,
) )
data class SecurityData(
val totalSecurityScore: Float,
val providerData: List<ProviderData>,
)
data class ProviderData(
val providerId: String,
val providerName: String,
val urlData: ProviderUrlData?,
val iconUrl: String,
val securityScore: Float,
val lastAuditDate: DateTime?,
)
data class ProviderUrlData(
val fullUrl: String,
val rootHost: String?,
)
data class Links( data class Links(
val officialLinks: List<Link>?, val officialLinks: List<Link>?,
val social: List<Link>?, val social: List<Link>?,

View file

@ -88,6 +88,9 @@ internal class MarketsTokenDetailsModel @Inject constructor(
// === Analytics === // === Analytics ===
analyticsEventHandler.send(analyticsEventBuilder.linkClicked(linkTitle = link.title)) analyticsEventHandler.send(analyticsEventBuilder.linkClicked(linkTitle = link.title))
}, },
onSecurityScoreProviderLinkClick = {
urlOpener.openUrl(it)
},
// === Analytics === // === Analytics ===
onPricePerformanceIntervalChanged = { onPricePerformanceIntervalChanged = {
analyticsEventHandler.send( analyticsEventHandler.send(

View file

@ -1,35 +1,57 @@
package com.tangem.features.markets.details.impl.model.converters package com.tangem.features.markets.details.impl.model.converters
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList
import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.features.markets.details.impl.model.formatter.MarketsDateTimeFormatters
import com.tangem.features.markets.details.impl.ui.state.SecurityScoreBottomSheetContent
import com.tangem.features.markets.details.impl.ui.state.SecurityScoreUM import com.tangem.features.markets.details.impl.ui.state.SecurityScoreUM
import com.tangem.features.markets.impl.R import com.tangem.features.markets.impl.R
import com.tangem.utils.converter.Converter import com.tangem.utils.converter.Converter
// TODO implement when backend is ready
@Stable @Stable
internal class SecurityScoreConverter( internal class SecurityScoreConverter(
private val onInfoClick: (InfoBottomSheetContent) -> Unit, private val onInfoClick: (SecurityScoreBottomSheetContent) -> Unit,
) : Converter<Unit, SecurityScoreUM> { private val onSecurityScoreProviderLinkClick: (String) -> Unit,
) : Converter<TokenMarketInfo.SecurityData, SecurityScoreUM> {
override fun convert(value: Unit): SecurityScoreUM { override fun convert(value: TokenMarketInfo.SecurityData): SecurityScoreUM {
return with(value) { val ratingsCount = value.providerData.size
SecurityScoreUM( return SecurityScoreUM(
score = 4.7f, score = value.totalSecurityScore,
description = "Based on 3 ratings", description = pluralReference(
id = R.plurals.markets_token_details_based_on_ratings,
count = ratingsCount,
formatArgs = wrappedList(ratingsCount),
),
onInfoClick = { onInfoClick = {
onInfoClick( onInfoClick(
InfoBottomSheetContent( SecurityScoreBottomSheetContent(
title = resourceReference(R.string.markets_token_details_security_score), title = resourceReference(R.string.markets_token_details_security_score),
body = stringReference("markets_token_details_security_score_description"), description = resourceReference(R.string.markets_token_details_security_score_description),
// FIXME providers = value.providerData.map {
// resourceReference(R.string.markets_token_details_security_score_description) SecurityScoreBottomSheetContent.SecurityScoreProviderUM(
name = it.providerName,
lastAuditDate = it.lastAuditDate?.let { date ->
MarketsDateTimeFormatters.formatAsDate(date.millis)
},
score = it.securityScore,
urlData = it.urlData?.let {
urlData ->
SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData(
fullUrl = urlData.fullUrl,
rootHost = urlData.rootHost,
)
},
iconUrl = it.iconUrl,
)
},
onProviderLinkClick = onSecurityScoreProviderLinkClick,
), ),
) )
}, },
) )
} }
} }
}

View file

@ -1,10 +1,10 @@
package com.tangem.features.markets.details.impl.model.converters package com.tangem.features.markets.details.impl.model.converters
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.PriceChangeInterval import com.tangem.domain.markets.PriceChangeInterval
import com.tangem.domain.markets.TokenMarketInfo import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent
import com.tangem.features.markets.details.impl.ui.state.LinksUM import com.tangem.features.markets.details.impl.ui.state.LinksUM
import com.tangem.features.markets.details.impl.ui.state.ListedOnUM import com.tangem.features.markets.details.impl.ui.state.ListedOnUM
import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM
@ -12,11 +12,13 @@ import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter import com.tangem.utils.converter.Converter
@Stable @Stable
@Suppress("LongParameterList")
internal class TokenMarketInfoConverter( internal class TokenMarketInfoConverter(
private val appCurrency: Provider<AppCurrency>, private val appCurrency: Provider<AppCurrency>,
private val onInfoClick: (InfoBottomSheetContent) -> Unit, private val onInfoClick: (TangemBottomSheetConfigContent) -> Unit,
private val onListedOnClick: (Int) -> Unit, private val onListedOnClick: (Int) -> Unit,
onLinkClick: (LinksUM.Link) -> Unit, onLinkClick: (LinksUM.Link) -> Unit,
onSecurityScoreProviderLinkClick: (String) -> Unit,
onPricePerformanceIntervalChanged: (PriceChangeInterval) -> Unit, onPricePerformanceIntervalChanged: (PriceChangeInterval) -> Unit,
onInsightsIntervalChanged: (PriceChangeInterval) -> Unit, onInsightsIntervalChanged: (PriceChangeInterval) -> Unit,
) : Converter<TokenMarketInfo, MarketsTokenDetailsUM.InformationBlocks> { ) : Converter<TokenMarketInfo, MarketsTokenDetailsUM.InformationBlocks> {
@ -27,9 +29,10 @@ internal class TokenMarketInfoConverter(
onIntervalChanged = onInsightsIntervalChanged, onIntervalChanged = onInsightsIntervalChanged,
) )
@Suppress("UnusedPrivateMember") private val securityScoreConverter = SecurityScoreConverter(
// TODO second markets iteration onInfoClick = onInfoClick,
private val securityScoreConverter = SecurityScoreConverter(onInfoClick = onInfoClick) onSecurityScoreProviderLinkClick = onSecurityScoreProviderLinkClick,
)
private val pricePerformanceConverter = PricePerformanceConverter( private val pricePerformanceConverter = PricePerformanceConverter(
appCurrency = appCurrency, appCurrency = appCurrency,
onIntervalChanged = onPricePerformanceIntervalChanged, onIntervalChanged = onPricePerformanceIntervalChanged,
@ -46,7 +49,7 @@ internal class TokenMarketInfoConverter(
val exchangesAmount = value.exchangesAmount val exchangesAmount = value.exchangesAmount
return MarketsTokenDetailsUM.InformationBlocks( return MarketsTokenDetailsUM.InformationBlocks(
insights = value.insights?.let { insightsConverter.convert(it) }, insights = value.insights?.let { insightsConverter.convert(it) },
securityScore = null, securityScore = value.securityData?.let { securityScoreConverter.convert(it) },
metrics = value.metrics?.let { metricsConverter.convert(it) }, metrics = value.metrics?.let { metricsConverter.convert(it) },
pricePerformance = value.pricePerformance?.let { pricePerformance = value.pricePerformance?.let {
pricePerformanceConverter.convert( pricePerformanceConverter.convert(

View file

@ -55,7 +55,7 @@ internal object MarketsDateTimeFormatters {
R.string.common_range_with_space, R.string.common_range_with_space,
wrappedList( wrappedList(
stringReference( stringReference(
startTimestamp.formatAsDateTime(MarketsDateTimeFormatters.dateTimeMMMFormatter), startTimestamp.formatAsDateTime(dateTimeMMMFormatter),
), ),
resourceReference(R.string.common_now), resourceReference(R.string.common_now),
), ),
@ -68,7 +68,7 @@ internal object MarketsDateTimeFormatters {
R.string.common_range_with_space, R.string.common_range_with_space,
wrappedList( wrappedList(
stringReference( stringReference(
startTimestamp.formatAsDateTime(MarketsDateTimeFormatters.dateFormatter), startTimestamp.formatAsDateTime(dateFormatter),
), ),
resourceReference(R.string.common_now), resourceReference(R.string.common_now),
), ),
@ -89,7 +89,7 @@ internal object MarketsDateTimeFormatters {
R.string.common_range_with_space, R.string.common_range_with_space,
wrappedList( wrappedList(
stringReference( stringReference(
markerTimestamp.toLong().formatAsDateTime(MarketsDateTimeFormatters.dateTimeMMMFormatter), markerTimestamp.toLong().formatAsDateTime(dateTimeMMMFormatter),
), ),
resourceReference(R.string.common_now), resourceReference(R.string.common_now),
), ),
@ -103,7 +103,7 @@ internal object MarketsDateTimeFormatters {
R.string.common_range_with_space, R.string.common_range_with_space,
wrappedList( wrappedList(
stringReference( stringReference(
markerTimestamp.toLong().formatAsDateTime(MarketsDateTimeFormatters.dateFormatter), markerTimestamp.toLong().formatAsDateTime(dateFormatter),
), ),
resourceReference(R.string.common_now), resourceReference(R.string.common_now),
), ),
@ -128,10 +128,14 @@ internal object MarketsDateTimeFormatters {
fun getDefaultDateTimeString(interval: PriceChangeInterval, currentTimestamp: Long): TextReference { fun getDefaultDateTimeString(interval: PriceChangeInterval, currentTimestamp: Long): TextReference {
return formatDateByInterval( return formatDateByInterval(
interval = interval, interval = interval,
startTimestamp = MarketsDateTimeFormatters.getStartTimestampByInterval( startTimestamp = getStartTimestampByInterval(
interval = interval, interval = interval,
currentTimestamp = currentTimestamp, currentTimestamp = currentTimestamp,
), ),
) )
} }
fun formatAsDate(timestamp: Long): String {
return timestamp.formatAsDateTime(dateFormatter)
}
} }

View file

@ -37,6 +37,7 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.markets.PriceChangeInterval import com.tangem.domain.markets.PriceChangeInterval
import com.tangem.features.markets.details.impl.ui.components.*
import com.tangem.features.markets.details.impl.ui.components.ExchangesBottomSheet import com.tangem.features.markets.details.impl.ui.components.ExchangesBottomSheet
import com.tangem.features.markets.details.impl.ui.components.InfoBottomSheet import com.tangem.features.markets.details.impl.ui.components.InfoBottomSheet
import com.tangem.features.markets.details.impl.ui.components.MarketTokenDetailsChart import com.tangem.features.markets.details.impl.ui.components.MarketTokenDetailsChart
@ -44,6 +45,7 @@ import com.tangem.features.markets.details.impl.ui.components.tokenMarketDetails
import com.tangem.features.markets.details.impl.ui.state.ExchangesBottomSheetContent import com.tangem.features.markets.details.impl.ui.state.ExchangesBottomSheetContent
import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent
import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM
import com.tangem.features.markets.details.impl.ui.state.SecurityScoreBottomSheetContent
import com.tangem.features.markets.impl.R import com.tangem.features.markets.impl.R
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -72,6 +74,7 @@ internal fun MarketsTokenDetailsContent(
when (state.bottomSheetConfig.content) { when (state.bottomSheetConfig.content) {
is InfoBottomSheetContent -> InfoBottomSheet(config = state.bottomSheetConfig) is InfoBottomSheetContent -> InfoBottomSheet(config = state.bottomSheetConfig)
is SecurityScoreBottomSheetContent -> SecurityScoreBottomSheet(config = state.bottomSheetConfig)
is ExchangesBottomSheetContent -> ExchangesBottomSheet(config = state.bottomSheetConfig) is ExchangesBottomSheetContent -> ExchangesBottomSheet(config = state.bottomSheetConfig)
} }
} }

View file

@ -0,0 +1,96 @@
package com.tangem.features.markets.details.impl.ui.components
import androidx.annotation.FloatRange
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.markets.impl.R
import kotlin.math.round
private const val STARS_COUNT = 5
@Composable
internal fun ScoreStarsBlock(
score: Float,
horizontalSpacing: Dp,
scoreTextStyle: TextStyle,
modifier: Modifier = Modifier,
) {
val rounded = score.roundTo1decimal()
val percentage = rounded / STARS_COUNT
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(horizontalSpacing),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = rounded.toString(),
style = scoreTextStyle,
color = TangemTheme.colors.text.primary1,
)
Stars(fraction = percentage)
}
}
@Suppress("MagicNumber")
@Composable
private fun Stars(@FloatRange(0.0, 1.0) fraction: Float = 0f) {
val grayColor = TangemTheme.colors.icon.inactive
Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
verticalAlignment = Alignment.CenterVertically,
) {
repeat(times = 5) { i ->
Box(
modifier = Modifier.size(TangemTheme.dimens.size16),
contentAlignment = Alignment.Center,
) {
Icon(
modifier = Modifier
.requiredSize(16.dp)
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
.drawWithCache {
onDrawWithContent {
val starFraction = ((fraction - i * 0.2) / 0.2).coerceIn(0.0, 1.0)
val starFractionFloat = starFraction
.toFloat()
.roundTo1decimal()
drawContent()
drawRect(
color = grayColor,
topLeft = Offset(x = size.width * starFractionFloat, y = 0f),
size = Size(size.width * (1 - starFractionFloat), size.height),
blendMode = BlendMode.SrcIn,
)
}
},
imageVector = ImageVector.vectorResource(R.drawable.ic_star_24),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
)
}
}
}
}
@Suppress("MagicNumber")
private fun Float.roundTo1decimal(): Float {
return round(this * 10) / 10
}

View file

@ -1,42 +1,28 @@
package com.tangem.features.markets.details.impl.ui.components package com.tangem.features.markets.details.impl.ui.components
import android.content.res.Configuration import android.content.res.Configuration
import androidx.annotation.FloatRange
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.block.information.InformationBlock import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.core.ui.components.text.TooltipText import com.tangem.core.ui.components.text.TooltipText
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.PreviewShimmerContainer import com.tangem.core.ui.utils.PreviewShimmerContainer
import com.tangem.features.markets.details.impl.ui.state.SecurityScoreUM import com.tangem.features.markets.details.impl.ui.state.SecurityScoreUM
import com.tangem.features.markets.impl.R import com.tangem.features.markets.impl.R
import kotlin.math.round
private const val STARS_COUNT = 5
@Composable @Composable
internal fun SecurityScoreBlock(state: SecurityScoreUM, modifier: Modifier = Modifier) { internal fun SecurityScoreBlock(state: SecurityScoreUM, modifier: Modifier = Modifier) {
val rounded = state.score.roundTo1decimal()
val percentage = rounded / STARS_COUNT
InformationBlock( InformationBlock(
modifier = modifier, modifier = modifier,
title = { title = {
@ -51,7 +37,7 @@ internal fun SecurityScoreBlock(state: SecurityScoreUM, modifier: Modifier = Mod
) )
Text( Text(
text = state.description, text = state.description.resolveReference(),
style = TangemTheme.typography.body2, style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary, color = TangemTheme.colors.text.tertiary,
maxLines = 1, maxLines = 1,
@ -60,72 +46,18 @@ internal fun SecurityScoreBlock(state: SecurityScoreUM, modifier: Modifier = Mod
} }
}, },
action = { action = {
Row( ScoreStarsBlock(
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing6), modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing6),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), score = state.score,
verticalAlignment = Alignment.CenterVertically, scoreTextStyle = TangemTheme.typography.body1,
) { horizontalSpacing = TangemTheme.dimens.spacing8,
Text(
text = rounded.toString(),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
) )
Stars(fraction = percentage)
}
}, },
) )
} }
@Suppress("MagicNumber")
@Composable @Composable
private fun Stars(@FloatRange(0.0, 1.0) fraction: Float = 0f) { private fun SecurityScorePlaceHolder(modifier: Modifier = Modifier) {
val grayColor = TangemTheme.colors.icon.inactive
Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
verticalAlignment = Alignment.CenterVertically,
) {
repeat(times = 5) { i ->
Box(
modifier = Modifier.size(TangemTheme.dimens.size16),
contentAlignment = Alignment.Center,
) {
Icon(
modifier = Modifier
.requiredSize(13.dp)
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
.drawWithCache {
onDrawWithContent {
val starFraction = ((fraction - i * 0.2) / 0.2).coerceIn(0.0, 1.0)
val starFractionFloat = starFraction
.toFloat()
.roundTo1decimal()
drawContent()
drawRect(
color = grayColor,
topLeft = Offset(x = size.width * starFractionFloat, y = 0f),
size = Size(size.width * (1 - starFractionFloat), size.height),
blendMode = BlendMode.SrcIn,
)
}
},
imageVector = ImageVector.vectorResource(R.drawable.ic_star_24),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
)
}
}
}
}
@Suppress("MagicNumber")
private fun Float.roundTo1decimal(): Float {
return round(this * 10) / 10
}
@Composable
internal fun SecurityScorePlaceHolder(modifier: Modifier = Modifier) {
InformationBlock( InformationBlock(
modifier = modifier, modifier = modifier,
title = { title = {
@ -177,7 +109,7 @@ private fun ContentPreview() {
SecurityScoreBlock( SecurityScoreBlock(
state = SecurityScoreUM( state = SecurityScoreUM(
score = 3.5f, score = 3.5f,
description = "Based on 3 ratings", description = stringReference("Based on 3 ratings"),
onInfoClick = {}, onInfoClick = {},
), ),
) )

View file

@ -0,0 +1,191 @@
package com.tangem.features.markets.details.impl.ui.components
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.util.fastForEachIndexed
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetTitle
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.markets.details.impl.ui.preview.SecurityScorePreviewData
import com.tangem.features.markets.impl.R
import com.tangem.features.markets.details.impl.ui.state.SecurityScoreBottomSheetContent
@Composable
internal fun SecurityScoreBottomSheet(config: TangemBottomSheetConfig) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
TangemBottomSheet<SecurityScoreBottomSheetContent>(
config = config,
containerColor = TangemTheme.colors.background.secondary,
addBottomInsets = false,
title = { TangemBottomSheetTitle(title = it.title) },
content = { content ->
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(horizontal = TangemTheme.dimens.spacing16),
) {
Text(
text = content.description.resolveReference(),
style = TangemTheme.typography.body2.copy(
color = TangemTheme.colors.text.secondary,
),
)
SpacerH12()
content.providers.fastForEachIndexed { index, provider ->
DividerContainer(
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = content.providers.lastIndex,
addDefaultPadding = false,
)
.background(TangemTheme.colors.background.action),
showDivider = index != content.providers.lastIndex,
) {
SecurityScoreProviderRow(
providerUM = provider,
onLinkClick = content.onProviderLinkClick,
)
}
}
SpacerH16()
SpacerH(bottomBarHeight)
}
},
)
}
@Composable
private fun SecurityScoreProviderRow(
providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM,
onLinkClick: (String) -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing12)
.heightIn(min = TangemTheme.dimens.size68),
verticalAlignment = Alignment.CenterVertically,
) {
SubcomposeAsyncImage(
modifier = Modifier
.size(size = TangemTheme.dimens.size40)
.clip(TangemTheme.shapes.roundedCorners8),
model = ImageRequest.Builder(context = LocalContext.current)
.data(providerUM.iconUrl)
.crossfade(enable = true)
.allowHardware(false)
.build(),
loading = { RectangleShimmer(radius = TangemTheme.dimens.radius8) },
error = { RectangleShimmer(radius = TangemTheme.dimens.radius8) },
contentDescription = null,
)
Column(
modifier = Modifier.padding(start = TangemTheme.dimens.spacing12),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
) {
Text(
text = providerUM.name,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
providerUM.lastAuditDate?.let {
Text(
text = providerUM.lastAuditDate,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
SpacerWMax()
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
modifier = Modifier.clickable(
enabled = providerUM.urlData != null,
indication = ripple(bounded = false),
interactionSource = remember { MutableInteractionSource() },
onClick = { providerUM.urlData?.let { onLinkClick(it.fullUrl) } },
),
) {
ScoreStarsBlock(
score = providerUM.score,
scoreTextStyle = TangemTheme.typography.body2,
horizontalSpacing = TangemTheme.dimens.spacing3,
)
UrlBlock(providerUM)
}
}
}
@Composable
private fun UrlBlock(providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM) {
val urlData = providerUM.urlData
val rootHost = urlData?.rootHost
if (urlData != null && rootHost != null) {
Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
) {
Text(
text = urlData.rootHost,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
Icon(
modifier = Modifier
.size(TangemTheme.dimens.size16),
painter = painterResource(id = R.drawable.ic_arrow_top_right_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun SecurityScoreBottomSheetPreview() {
TangemThemePreview {
SecurityScoreBottomSheet(
config = TangemBottomSheetConfig(
isShow = true,
onDismissRequest = {},
content = SecurityScorePreviewData.bottomSheetContent,
),
)
}
}

View file

@ -0,0 +1,60 @@
package com.tangem.features.markets.details.impl.ui.preview
import com.tangem.core.ui.extensions.stringReference
import com.tangem.features.markets.details.impl.ui.state.SecurityScoreBottomSheetContent
internal object SecurityScorePreviewData {
val bottomSheetContent = SecurityScoreBottomSheetContent(
title = stringReference("Security score"),
description = stringReference(
"Security score of a token is a metric that assesses the " +
"security level of a blockchain or token based on various factors and is compiled from " +
"the sources listed below.",
),
providers = listOf(
SecurityScoreBottomSheetContent.SecurityScoreProviderUM(
name = "Moralis",
lastAuditDate = "21.10.2024",
score = 4.9F,
urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData(
fullUrl = "https://moralis.com/",
rootHost = "moralis.com",
),
iconUrl = "",
),
SecurityScoreBottomSheetContent.SecurityScoreProviderUM(
name = "Certik",
lastAuditDate = "10.07.2024",
score = 4.6F,
urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData(
fullUrl = "https://certik.com/",
rootHost = "certik.com",
),
iconUrl = "",
),
SecurityScoreBottomSheetContent.SecurityScoreProviderUM(
name = "Cyberscope",
lastAuditDate = "25.06.2023",
score = 4.5F,
urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData(
fullUrl = "https://cyberscope.com/",
rootHost = "cyberscope.com",
),
iconUrl = "",
),
SecurityScoreBottomSheetContent.SecurityScoreProviderUM(
name = "TokenInsight",
lastAuditDate = "17.01.2022",
score = 4.0F,
urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData(
fullUrl = "https://tokeninsight.com/",
rootHost = "tokeninsight.com",
),
iconUrl = "",
),
),
onProviderLinkClick = {},
)
}

View file

@ -0,0 +1,25 @@
package com.tangem.features.markets.details.impl.ui.state
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.extensions.TextReference
internal data class SecurityScoreBottomSheetContent(
val title: TextReference,
val description: TextReference,
val providers: List<SecurityScoreProviderUM>,
val onProviderLinkClick: (String) -> Unit,
) : TangemBottomSheetConfigContent {
data class SecurityScoreProviderUM(
val name: String,
val lastAuditDate: String?,
val score: Float,
val urlData: SecurityScoreProviderUrlData?,
val iconUrl: String?,
)
data class SecurityScoreProviderUrlData(
val fullUrl: String,
val rootHost: String?,
)
}

View file

@ -1,9 +1,10 @@
package com.tangem.features.markets.details.impl.ui.state package com.tangem.features.markets.details.impl.ui.state
import androidx.annotation.FloatRange import androidx.annotation.FloatRange
import com.tangem.core.ui.extensions.TextReference
internal data class SecurityScoreUM( internal data class SecurityScoreUM(
@FloatRange(from = 0.0, to = 5.0) val score: Float, @FloatRange(from = 0.0, to = 5.0) val score: Float,
val description: String, val description: TextReference,
val onInfoClick: () -> Unit, val onInfoClick: () -> Unit,
) )